Merge "Avoid NPE by fixing ResearchLogger initialization"
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 4a8b955..fb973f3 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -32,7 +32,7 @@
     <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
 
     <application android:label="@string/english_ime_name"
-            android:icon="@mipmap/ic_ime_settings"
+            android:icon="@mipmap/ic_launcher_keyboard"
             android:killAfterRestore="false"
             android:supportsRtl="true">
 
@@ -56,13 +56,23 @@
 
         <activity android:name=".setup.SetupActivity"
                 android:label="@string/english_ime_name"
-                android:icon="@drawable/ic_setup_wizard">
+                android:icon="@mipmap/ic_launcher_keyboard"
+                android:launchMode="singleTask"
+                android:noHistory="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
+        <activity android:name=".setup.SetupWizardActivity"
+                android:label="@string/english_ime_name"
+                android:clearTaskOnLaunch="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
         <receiver android:name=".setup.LauncherIconVisibilityManager">
             <intent-filter>
                 <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
@@ -125,7 +135,6 @@
 
         <activity android:name="com.android.inputmethod.dictionarypack.DictionarySettingsActivity"
                   android:label="@string/dictionary_settings_title"
-                  android:icon="@mipmap/ic_ime_settings"
                   android:theme="@android:style/Theme.Holo"
                   android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
@@ -135,7 +144,6 @@
 
         <activity android:name="com.android.inputmethod.dictionarypack.DownloadOverMeteredDialog"
                   android:label="@string/dictionary_install_over_metered_network_prompt"
-                  android:icon="@mipmap/ic_ime_settings"
                   android:theme="@android:style/Theme.Holo">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/java/res/drawable-hdpi/ic_setup_wizard.png b/java/res/drawable-hdpi/ic_setup_wizard.png
deleted file mode 100644
index 38fca6d..0000000
--- a/java/res/drawable-hdpi/ic_setup_wizard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_setup_wizard.png b/java/res/drawable-mdpi/ic_setup_wizard.png
deleted file mode 100644
index 66e62b8..0000000
--- a/java/res/drawable-mdpi/ic_setup_wizard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_setup_wizard.png b/java/res/drawable-xhdpi/ic_setup_wizard.png
deleted file mode 100644
index 53f70a6..0000000
--- a/java/res/drawable-xhdpi/ic_setup_wizard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_setup_wizard.png b/java/res/drawable-xxhdpi/ic_setup_wizard.png
deleted file mode 100644
index 6414b4f..0000000
--- a/java/res/drawable-xxhdpi/ic_setup_wizard.png
+++ /dev/null
Binary files differ
diff --git a/java/res/layout/setup_wizard.xml b/java/res/layout/setup_wizard.xml
index 176f836..87db4d0 100644
--- a/java/res/layout/setup_wizard.xml
+++ b/java/res/layout/setup_wizard.xml
@@ -19,6 +19,7 @@
 -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/setup_wizard"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/setup_background"
diff --git a/java/res/layout/user_dictionary_add_word_fullscreen.xml b/java/res/layout/user_dictionary_add_word_fullscreen.xml
index 75e86c5..219485b 100644
--- a/java/res/layout/user_dictionary_add_word_fullscreen.xml
+++ b/java/res/layout/user_dictionary_add_word_fullscreen.xml
@@ -19,12 +19,6 @@
     android:layout_height="wrap_content"
     android:orientation="vertical" >
 
-    <TextView
-        style="?android:attr/listSeparatorTextViewStyle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/user_dict_settings_add_screen_title" />
-
     <EditText
         android:id="@+id/user_dictionary_add_word_text"
         android:layout_width="match_parent"
diff --git a/java/res/mipmap-hdpi/ic_ime_settings.png b/java/res/mipmap-hdpi/ic_ime_settings.png
deleted file mode 100644
index 486c70d..0000000
--- a/java/res/mipmap-hdpi/ic_ime_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/mipmap-hdpi/ic_launcher_keyboard.png b/java/res/mipmap-hdpi/ic_launcher_keyboard.png
new file mode 100644
index 0000000..36b1cca
--- /dev/null
+++ b/java/res/mipmap-hdpi/ic_launcher_keyboard.png
Binary files differ
diff --git a/java/res/mipmap-mdpi/ic_ime_settings.png b/java/res/mipmap-mdpi/ic_ime_settings.png
deleted file mode 100644
index 75f4afb..0000000
--- a/java/res/mipmap-mdpi/ic_ime_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/mipmap-mdpi/ic_launcher_keyboard.png b/java/res/mipmap-mdpi/ic_launcher_keyboard.png
new file mode 100644
index 0000000..67ef189
--- /dev/null
+++ b/java/res/mipmap-mdpi/ic_launcher_keyboard.png
Binary files differ
diff --git a/java/res/mipmap-xhdpi/ic_ime_settings.png b/java/res/mipmap-xhdpi/ic_ime_settings.png
deleted file mode 100644
index bbf1919..0000000
--- a/java/res/mipmap-xhdpi/ic_ime_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/mipmap-xhdpi/ic_launcher_keyboard.png b/java/res/mipmap-xhdpi/ic_launcher_keyboard.png
new file mode 100644
index 0000000..b332083
--- /dev/null
+++ b/java/res/mipmap-xhdpi/ic_launcher_keyboard.png
Binary files differ
diff --git a/java/res/mipmap-xxhdpi/ic_ime_settings.png b/java/res/mipmap-xxhdpi/ic_ime_settings.png
deleted file mode 100644
index 16fc693..0000000
--- a/java/res/mipmap-xxhdpi/ic_ime_settings.png
+++ /dev/null
Binary files differ
diff --git a/java/res/mipmap-xxhdpi/ic_launcher_keyboard.png b/java/res/mipmap-xxhdpi/ic_launcher_keyboard.png
new file mode 100644
index 0000000..acc424f
--- /dev/null
+++ b/java/res/mipmap-xxhdpi/ic_launcher_keyboard.png
Binary files differ
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index dbf39c5..502355e 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -57,7 +57,7 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"همیشه نمایش داده شود"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"نمایش در حالت عمودی"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"همیشه پنهان شود"</string>
-    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"مسدودکردن کلمات توهین‌آمیز"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"عدم نمایش کلمات توهین‌آمیز"</string>
     <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"کلمات توهین‌آمیز احتمالی پیشنهاد نشود"</string>
     <string name="auto_correction" msgid="7630720885194996950">"تصحیح خودکار"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"کلید فاصله و علائم نگارشی به صورت خودکار کلماتی را که غلط تایپ شده‌اند تصحیح می‌کنند"</string>
@@ -181,7 +181,7 @@
     <string name="setup_steps_title" msgid="6400373034871816182">"راه‌اندازی <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step1_title" msgid="3147967630253462315">"فعال‌سازی <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step1_instruction" msgid="2578631936624637241">"لطفاً «<xliff:g id="APPLICATION_NAME">%s</xliff:g>» را در تنظیمات زبان و ورودی خود علامت بزنید. این کار مجوز اجرای آن در دستگاه شما است."</string>
-    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> در حال حاضر در تنظیمات زبان و ورودی شما فعال است، بنابراین این مرحله انجام شده است. به مرحله بعدی بروید!"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> در حال حاضر در تنظیمات زبان و ورودی شما فعال است، بنابراین این مرحله انجام شده است. به مرحله بعد بروید!"</string>
     <string name="setup_step1_action" msgid="4366513534999901728">"فعال‌سازی در تنظیمات"</string>
     <string name="setup_step2_title" msgid="6860725447906690594">"جابجایی به <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step2_instruction" msgid="9141481964870023336">"در مرحله بعد، با انتخاب «<xliff:g id="APPLICATION_NAME">%s</xliff:g>» به عنوان روش ورودی نوشتار خود آن را فعال نمایید."</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index b12c36e..167b43b 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -24,9 +24,9 @@
     <string name="english_ime_research_log" msgid="8492602295696577851">"Naplózási parancsok"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Névjegyek keresése"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"A helyesírás-ellenőrző használja a névjegyek bejegyzéseit"</string>
-    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rezgés billentyű megnyomása esetén"</string>
-    <string name="sound_on_keypress" msgid="6093592297198243644">"Hangjelzés billentyű megnyomása esetén"</string>
-    <string name="popup_on_keypress" msgid="123894815723512944">"Legyen nagyobb billentyű lenyomásakor"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rezgés gombnyomásra"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Hangjelzés gombnyomásra"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Nagyobb billentyű gombnyomásra"</string>
     <string name="general_category" msgid="1859088467017573195">"Általános"</string>
     <string name="correction_category" msgid="2236750915056607613">"Szövegjavítás"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"Kézmozdulatokkal történő gépelés"</string>
@@ -124,7 +124,7 @@
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"A fő billentyűzeten"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Szimbólumoknál"</string>
     <string name="voice_input_modes_off" msgid="3745699748218082014">"Ki"</string>
-    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikr. a billentyűzeten"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mikrofon a billentyűzeten"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. a szimbólumoknál"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hangbevivel KI"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Beviteli módok beállítása"</string>
diff --git a/java/res/values-ms/strings-appname.xml b/java/res/values-ms/strings-appname.xml
new file mode 100644
index 0000000..1f9501a
--- /dev/null
+++ b/java/res/values-ms/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Papan kekunci Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Penyemak Ejaan Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Tetapan Papan Kekunci Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Tetapan Penyemak Ejaan Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index 6b6df5d..fe1a3cd 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -57,10 +57,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Sentiasa tunjukkan"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Tunjukkan dalam mod potret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sentiasa sembunyikan"</string>
-    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
-    <skip />
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Sekat perkataan yg menyinggung"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Jangan cadangkan perkataan yang boleh menyinggung"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Auto pembetulan"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Bar ruang dan tanda baca secara automatik membetulkan perkataan yang ditaip salah"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Matikan"</string>
@@ -179,8 +177,7 @@
     <string name="setup_steps_title" msgid="6400373034871816182">"Menyediakan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step1_title" msgid="3147967630253462315">"Dayakan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step1_instruction" msgid="2578631936624637241">"Sila semak \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" dlm ttpn Bhs &amp; input. Ini mbnarkn apl djlnkn pd pranti anda."</string>
-    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
-    <skip />
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> sudah didayakan dlm ttpn Bahasa &amp; input anda, jd langkah ini tlh selesai. Beralih ke langkah seterusnya!"</string>
     <string name="setup_step1_action" msgid="4366513534999901728">"Dayakan dalam Tetapan"</string>
     <string name="setup_step2_title" msgid="6860725447906690594">"Beralih ke <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step2_instruction" msgid="9141481964870023336">"Seterusnya, pilih \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" sebagai kaedah input teks aktif anda."</string>
@@ -222,42 +219,23 @@
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Tekan untuk mengulas dan memuat turun"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Memuat turun: cadangan untuk <xliff:g id="LANGUAGE">%1$s</xliff:g> akan sedia tidak lama lagi."</string>
     <string name="version_text" msgid="2715354215568469385">"Versi <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
-    <skip />
-    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
-    <skip />
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Tambah"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Tambah ke kamus"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frasa"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Lagi pilihan"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Kurang pilihan"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Perkataan:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Pintasan:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Bahasa:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Taip perkataan"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Pintasan pilihan"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edit perkataan"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Padam"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Anda tidak mempunyai sebarang perkataan dalam kamus pengguna. Tambah perkataan dengan menyentuh butang Tambah (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Untuk semua bahasa"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Lebih banyak bahasa..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Padam"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-pt/strings-appname.xml b/java/res/values-pt/strings-appname.xml
new file mode 100644
index 0000000..7180a0c
--- /dev/null
+++ b/java/res/values-pt/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Teclado Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Corretor ortográfico do Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Configurações de teclado Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Configurações de corretor ortográfico do Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index a58d16d..25592b1 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -57,10 +57,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostrar em modo de retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sempre ocultar"</string>
-    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
-    <skip />
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquear palavras ofensivas"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Não sugerir palavras potencialmente ofensivas"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Correção automática"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"A barra de espaço e a pontuação corrigem automaticamente palavras com erro de digitação"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desativado"</string>
@@ -179,8 +177,7 @@
     <string name="setup_steps_title" msgid="6400373034871816182">"Configurando o <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step1_title" msgid="3147967630253462315">"Ative o <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step1_instruction" msgid="2578631936624637241">"Marque \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" em \"Configurações de idioma e entrada\" para autorizar a execução."</string>
-    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
-    <skip />
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> já está ativado em suas configurações de idioma e entrada. Esta estapa está concluída. Vamos avançar para a próxima!"</string>
     <string name="setup_step1_action" msgid="4366513534999901728">"Ativar em \"Configurações\""</string>
     <string name="setup_step2_title" msgid="6860725447906690594">"Abra o <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
     <string name="setup_step2_instruction" msgid="9141481964870023336">"Em seguida, selecione \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" como o método de entrada de texto ativo."</string>
@@ -222,42 +219,23 @@
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Pressione para consultar e fazer o download"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Download em andamento: as sugestões para <xliff:g id="LANGUAGE">%1$s</xliff:g> estarão prontas em breve."</string>
     <string name="version_text" msgid="2715354215568469385">"Versão <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
-    <skip />
-    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
-    <skip />
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Adicionar"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Adicionar ao dicionário"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Mais opções"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Menos opções"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Ok"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Palavra:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Atalho:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Idioma:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Digite uma palavra"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Atalho opcional"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Editar palavra"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Editar"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Excluir"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Você não possui palavras no dicionário do usuário. Adicione uma palavra tocando no botão \"Adicionar\" ou no símbolo \"+\"."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Para todos os idiomas"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Mais idiomas..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Excluir"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 06b99de..47f2132 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -124,7 +124,7 @@
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"На основ. клавіатурі"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Символьна клавіатура"</string>
     <string name="voice_input_modes_off" msgid="3745699748218082014">"Вимк."</string>
-    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Miкр. на осн. клав."</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Мікрофон на основній клавіатурі"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Miкр. на симв. клавіат."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голос. ввід вимкнено"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Налаштування методів введення"</string>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index a71e7cc..8a67336 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -108,6 +108,14 @@
         <attr name="backgroundDimAlpha" format="integer" />
         <!-- More keys keyboard will shown at touched point. -->
         <attr name="showMoreKeysKeyboardAtTouchedPoint" format="boolean" />
+        <!-- Minimum distance between gesture preview trail sampling points. -->
+        <attr name="gesturePreviewTrailMinSamplingDistance" format="dimension" />
+        <!-- Maximum angular threshold between gesture preview trail interpolation segments in degree. -->
+        <attr name="gesturePreviewTrailMaxInterpolationAngularThreshold" format="integer" />
+        <!-- Maximum distance threshold between gesture preview trail interpolation segments. -->
+        <attr name="gesturePreviewTrailMaxInterpolationDistanceThreshold" format="dimension" />
+        <!-- Maximum number of gesture preview trail interpolation segments. -->
+        <attr name="gesturePreviewTrailMaxInterpolationSegments" format="integer" />
         <!-- Delay after gesture trail starts fading out in millisecond. -->
         <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
         <!-- Duration while gesture preview trail is fading out in millisecond. -->
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index da735cf..5c33275 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -101,6 +101,14 @@
     <fraction name="center_suggestion_percentile">36%</fraction>
 
     <!-- Gesture preview trail parameters -->
+    <!-- Minimum distance between gesture preview trail sampling points. -->
+    <dimen name="gesture_preview_trail_min_sampling_distance">6.4dp</dimen>
+    <!-- Maximum angular threshold between gesture preview trails interpolation segments in degree. -->
+    <integer name="gesture_preview_trail_max_interpolation_angular_threshold">15</integer>
+    <!-- Maximum distance threshold between gesture preview trails interpolation segments. -->
+    <dimen name="gesture_preview_trail_max_interpolation_distance_threshold">16.0dp</dimen>
+    <!-- Maximum number of gesture preview trail interpolation segments. -->
+    <integer name="gesture_preview_trail_max_interpolation_segments">6</integer>
     <dimen name="gesture_preview_trail_start_width">10.0dp</dimen>
     <dimen name="gesture_preview_trail_end_width">2.5dp</dimen>
     <!-- Percentages of gesture preview taril body and shadow, in proportion to the trail width.
diff --git a/java/res/values/keyboard-heights.xml b/java/res/values/keyboard-heights.xml
index 418d3e5..1c0277c 100644
--- a/java/res/values/keyboard-heights.xml
+++ b/java/res/values/keyboard-heights.xml
@@ -19,20 +19,20 @@
 -->
 
 <resources>
-    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <!-- Build condition,keyboard_height_in_dp -->
     <string-array name="keyboard_heights" translatable="false">
     <!-- Preferable keyboard height in absolute scale: 1.285in -->
         <!-- Droid -->
-        <item>sholes,227.0167</item>
+        <item>HARDWARE=sholes,227.0167</item>
         <!-- Nexus One -->
-        <item>mahimahi,217.5932</item>
+        <item>HARDWARE=mahimahi,217.5932</item>
         <!-- Nexus S -->
-        <item>herring,200.8554</item>
+        <item>HARDWARE=herring,200.8554</item>
         <!-- Galaxy Nexus -->
-        <item>tuna,202.5869</item>
+        <item>HARDWARE=tuna,202.5869</item>
     <!-- Preferable keyboard height in absolute scale: 48.0mm -->
         <!-- Xoom -->
-        <item>stingray,283.1337</item>
+        <item>HARDWARE=stingray,283.1337</item>
     <!-- Default value for unknown device: empty string -->
         <item>DEFAULT,</item>
     </string-array>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 10400be..45c51e7 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -18,16 +18,16 @@
 */
 -->
 <resources>
-    <!-- Build.HARDWARE,duration_in_milliseconds -->
+    <!-- Build condition,duration_in_milliseconds -->
     <string-array name="keypress_vibration_durations" translatable="false">
         <!-- Nexus S -->
-        <item>herring,5</item>
+        <item>HARDWARE=herring,5</item>
         <!-- Galaxy Nexus -->
-        <item>tuna,5</item>
+        <item>HARDWARE=tuna,5</item>
         <!-- Nexus 4 -->
-        <item>mako,5</item>
+        <item>HARDWARE=mako,5</item>
         <!-- Nexus 10 -->
-        <item>manta,16</item>
+        <item>HARDWARE=manta,16</item>
         <!-- Default value for unknown device -->
         <item>DEFAULT,20</item>
     </string-array>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index 047fe0c..7061f13 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -18,14 +18,14 @@
 */
 -->
 <resources>
+    <!-- Build condition,volume -->
     <string-array name="keypress_volumes" translatable="false">
-        <!-- Build.HARDWARE,volume -->
-        <item>herring,0.5f</item>
-        <item>tuna,0.5f</item>
-        <item>stingray,0.4f</item>
-        <item>grouper,0.3f</item>
-        <item>mako,0.3f</item>
-        <item>manta,0.2f</item>
+        <item>HARDWARE=herring,0.5f</item>
+        <item>HARDWARE=tuna,0.5f</item>
+        <item>HARDWARE=stingray,0.4f</item>
+        <item>HARDWARE=grouper,0.3f</item>
+        <item>HARDWARE=mako,0.3f</item>
+        <item>HARDWARE=manta,0.2f</item>
         <!-- Default value for unknown device -->
         <item>DEFAULT,0.2f</item>
     </string-array>
diff --git a/java/res/values/phantom-sudden-move-event-device-list.xml b/java/res/values/phantom-sudden-move-event-device-list.xml
index 22f5102..d0895b1 100644
--- a/java/res/values/phantom-sudden-move-event-device-list.xml
+++ b/java/res/values/phantom-sudden-move-event-device-list.xml
@@ -19,10 +19,10 @@
 -->
 <resources>
     <string-array name="phantom_sudden_move_event_device_list" translatable="false">
-        <!-- "Build.HARDWARE,true" that needs "phantom sudden move event" hack.
+        <!-- "Build condition,true" that needs "phantom sudden move event" hack.
              See {@link com.android.inputmethod.keyboard.PointerTracker}. -->
         <!-- Xoom -->
-        <item>stingray,true</item>
+        <item>HARDWARE=stingray,true</item>
         <!-- Default value for unknown device -->
         <item>DEFAULT,false</item>
     </string-array>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index dad7e20..fa40e51 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -64,6 +64,10 @@
         <item name="gestureFloatingPreviewHorizontalPadding">@dimen/gesture_floating_preview_horizontal_padding</item>
         <item name="gestureFloatingPreviewVerticalPadding">@dimen/gesture_floating_preview_vertical_padding</item>
         <item name="gestureFloatingPreviewRoundRadius">@dimen/gesture_floating_preview_round_radius</item>
+        <item name="gesturePreviewTrailMinSamplingDistance">@dimen/gesture_preview_trail_min_sampling_distance</item>
+        <item name="gesturePreviewTrailMaxInterpolationAngularThreshold">@integer/gesture_preview_trail_max_interpolation_angular_threshold</item>
+        <item name="gesturePreviewTrailMaxInterpolationDistanceThreshold">@dimen/gesture_preview_trail_max_interpolation_distance_threshold</item>
+        <item name="gesturePreviewTrailMaxInterpolationSegments">@integer/gesture_preview_trail_max_interpolation_segments</item>
         <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
         <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
         <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
diff --git a/java/res/values/sudden-jumping-touch-event-device-list.xml b/java/res/values/sudden-jumping-touch-event-device-list.xml
index 3fdc0c7..73e30c1 100644
--- a/java/res/values/sudden-jumping-touch-event-device-list.xml
+++ b/java/res/values/sudden-jumping-touch-event-device-list.xml
@@ -19,12 +19,12 @@
 -->
 <resources>
     <string-array name="sudden_jumping_touch_event_device_list" translatable="false">
-        <!-- "Build.HARDWARE,true" that needs "sudden jump touch event" hack.
+        <!-- "Build condition,true" that needs "sudden jump touch event" hack.
              See {@link com.android.inputmethod.keyboard.SuddenJumpingTouchEventHandler}. -->
         <!-- Nexus One -->
-        <item>mahimahi,true</item>
+        <item>HARDWARE=mahimahi,true</item>
         <!-- Droid -->
-        <item>sholes,true</item>
+        <item>HARDWARE=sholes,true</item>
         <!-- Default value for unknown device -->
         <item>DEFAULT,false</item>
     </string-array>
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index fb75d6d..6183223 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -66,6 +66,8 @@
     private boolean mChangedSettings;
     private DictionaryListInterfaceState mDictionaryListInterfaceState =
             new DictionaryListInterfaceState();
+    private TreeMap<String, WordListPreference> mCurrentPreferenceMap =
+            new TreeMap<String, WordListPreference>(); // never null
 
     private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() {
             @Override
@@ -278,7 +280,7 @@
             return result;
         } else {
             final String systemLocaleString = Locale.getDefault().toString();
-            final TreeMap<String, WordListPreference> prefList =
+            final TreeMap<String, WordListPreference> prefMap =
                     new TreeMap<String, WordListPreference>();
             final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN);
             final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN);
@@ -299,16 +301,31 @@
                 // The key is sorted in lexicographic order, according to the match level, then
                 // the description.
                 final String key = matchLevelString + "." + description + "." + wordlistId;
-                final WordListPreference existingPref = prefList.get(key);
+                final WordListPreference existingPref = prefMap.get(key);
                 if (null == existingPref || hasPriority(status, existingPref.mStatus)) {
-                    final WordListPreference pref = new WordListPreference(activity,
-                            mDictionaryListInterfaceState, mClientId, wordlistId, version, locale,
-                            description, status, filesize);
-                    prefList.put(key, pref);
+                    final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
+                    final WordListPreference pref;
+                    if (null != oldPreference
+                            && oldPreference.mVersion == version
+                            && oldPreference.mLocale.equals(locale)) {
+                        // If the old preference has all the new attributes, reuse it. We test
+                        // for version and locale because although attributes other than status
+                        // need to be the same, others have been tested through the key of the
+                        // map. Also, status may differ so we don't want to use #equals() here.
+                        pref = oldPreference;
+                        pref.mStatus = status;
+                    } else {
+                        // Otherwise, discard it and create a new one instead.
+                        pref = new WordListPreference(activity, mDictionaryListInterfaceState,
+                                mClientId, wordlistId, version, locale, description, status,
+                                filesize);
+                    }
+                    prefMap.put(key, pref);
                 }
             } while (cursor.moveToNext());
             cursor.close();
-            return prefList.values();
+            mCurrentPreferenceMap = prefMap;
+            return prefMap.values();
         }
     }
 
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index 29015d6..451a0fb 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -58,6 +58,8 @@
     // The metadata word list id and version of this word list.
     public final String mWordlistId;
     public final int mVersion;
+    public final Locale mLocale;
+    public final String mDescription;
     // The status
     public int mStatus;
     // The size of the dictionary file
@@ -80,6 +82,8 @@
         mVersion = version;
         mWordlistId = wordlistId;
         mFilesize = filesize;
+        mLocale = locale;
+        mDescription = description;
 
         setLayoutResource(R.layout.dictionary_line);
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index bc9e8cd..1fe23a3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -29,17 +29,18 @@
 import android.content.res.XmlResourceParser;
 import android.text.InputType;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.KeysCache;
+import com.android.inputmethod.latin.AdditionalSubtype;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.InputTypeUtils;
@@ -72,6 +73,8 @@
     private static final String TAG_ELEMENT = "Element";
 
     private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_";
+    private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480;
+    private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 800;
 
     private final Context mContext;
     private final Params mParams;
@@ -282,8 +285,7 @@
             return this;
         }
 
-        @UsedForTesting
-        public void disableTouchPositionCorrectionDataForTest() {
+        public void disableTouchPositionCorrectionData() {
             mParams.mDisableTouchPositionCorrectionDataForTest = true;
         }
 
@@ -413,4 +415,47 @@
             }
         }
     }
+
+    public static KeyboardLayoutSet createKeyboardSetForSpellChecker(final Context context,
+            final String locale, final String layout) {
+        final InputMethodSubtype subtype =
+                AdditionalSubtype.createAdditionalSubtype(locale, layout, null);
+        return createKeyboardSet(context, subtype, SPELLCHECKER_DUMMY_KEYBOARD_WIDTH,
+                SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false);
+    }
+
+    public static KeyboardLayoutSet createKeyboardSetForTest(final Context context,
+            final InputMethodSubtype subtype, final int orientation,
+            final boolean testCasesHaveTouchCoordinates) {
+        final DisplayMetrics dm = context.getResources().getDisplayMetrics();
+        final int width;
+        final int height;
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            width = Math.max(dm.widthPixels, dm.heightPixels);
+            height = Math.min(dm.widthPixels, dm.heightPixels);
+        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+            width = Math.min(dm.widthPixels, dm.heightPixels);
+            height = Math.max(dm.widthPixels, dm.heightPixels);
+        } else {
+            throw new RuntimeException("Orientation should be ORIENTATION_LANDSCAPE or "
+                    + "ORIENTATION_PORTRAIT: orientation=" + orientation);
+        }
+        return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates);
+    }
+
+    private static KeyboardLayoutSet createKeyboardSet(final Context context,
+            final InputMethodSubtype subtype, final int width, final int height,
+            final boolean testCasesHaveTouchCoordinates) {
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
+        final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
+                context, editorInfo);
+        builder.setScreenGeometry(width, height);
+        builder.setSubtype(subtype);
+        if (!testCasesHaveTouchCoordinates) {
+            // For spell checker and tests
+            builder.disableTouchPositionCorrectionData();
+        }
+        return builder.build();
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 0556fdd..2d79164 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -25,6 +25,7 @@
 import com.android.inputmethod.keyboard.internal.GestureStroke;
 import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams;
 import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints;
+import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
@@ -161,6 +162,7 @@
     // Parameters for pointer handling.
     private static PointerTrackerParams sParams;
     private static GestureStrokeParams sGestureStrokeParams;
+    private static GestureStrokePreviewParams sGesturePreviewParams;
     private static boolean sNeedsPhantomSuddenMoveEventHack;
     // Move this threshold to resource.
     // TODO: Device specific parameter would be better for device specific hack?
@@ -339,12 +341,14 @@
         sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
         sParams = PointerTrackerParams.DEFAULT;
         sGestureStrokeParams = GestureStrokeParams.DEFAULT;
+        sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT;
         sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
     }
 
     public static void setParameters(final TypedArray mainKeyboardViewAttr) {
         sParams = new PointerTrackerParams(mainKeyboardViewAttr);
         sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr);
+        sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr);
         sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
     }
 
@@ -428,7 +432,7 @@
         }
         mPointerId = id;
         mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
-                id, sGestureStrokeParams);
+                id, sGestureStrokeParams, sGesturePreviewParams);
         setKeyDetectorInner(handler.getKeyDetector());
         mListener = handler.getKeyboardActionListener();
         mDrawingProxy = handler.getDrawingProxy();
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index b77e378..57d3fed 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -79,23 +79,6 @@
         mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
     }
 
-    /**
-     * Constructor for subclasses such as
-     * {@link com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo}.
-     */
-    protected ProximityInfo(final int[] proximityCharsArray, final int gridWidth,
-            final int gridHeight) {
-        this("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
-        mNativeProximityInfo = setProximityInfoNative("" /* locale */,
-                gridWidth /* displayWidth */, gridHeight /* displayHeight */,
-                gridWidth, gridHeight, 1 /* mostCommonKeyWidth */,
-                1 /* mostCommonKeyHeight */, proximityCharsArray, 0 /* keyCount */,
-                null /*keyXCoordinates */, null /* keyYCoordinates */,
-                null /* keyWidths */, null /* keyHeights */, null /* keyCharCodes */,
-                null /* sweetSpotCenterXs */, null /* sweetSpotCenterYs */,
-                null /* sweetSpotRadii */);
-    }
-
     private long mNativeProximityInfo;
     static {
         JniUtils.loadNativeLibrary();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index 7fd1bed..761d9dc 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -18,6 +18,7 @@
 
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Rect;
@@ -35,12 +36,19 @@
  * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailWidth
  */
 final class GesturePreviewTrail {
+    public static final boolean DBG_SHOW_POINTS = false;
+    public static final int POINT_TYPE_SAMPLED = 0;
+    public static final int POINT_TYPE_INTERPOLATED = 1;
+    public static final int POINT_TYPE_COMPROMISED = 2;
+
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
     // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}.
     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
+    private final ResizableIntArray mPointTypes = new ResizableIntArray(
+            DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
     private int mCurrentStrokeId = -1;
     // The wall time of the zero value in {@link #mEventTimes}
     private long mCurrentTimeBase;
@@ -75,9 +83,9 @@
                     R.styleable.MainKeyboardView_gesturePreviewTrailShadowRatio, 0);
             mTrailShadowEnabled = (trailShadowRatioInt > 0);
             mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
-            mFadeoutStartDelay = mainKeyboardViewAttr.getInt(
+            mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
-            mFadeoutDuration = mainKeyboardViewAttr.getInt(
+            mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutDuration, 0);
             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
             mUpdateInterval = mainKeyboardViewAttr.getInt(
@@ -125,7 +133,7 @@
         final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId)
                 ? mLastInterpolatedDrawIndex : trailSize;
         mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment(
-                lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates);
+                lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates, mPointTypes);
         if (strokeId != mCurrentStrokeId) {
             final int elapsedTime = (int)(downTime - mCurrentTimeBase);
             for (int i = mTrailStartIndex; i < trailSize; i++) {
@@ -204,6 +212,7 @@
         final int[] eventTimes = mEventTimes.getPrimitiveArray();
         final int[] xCoords = mXCoordinates.getPrimitiveArray();
         final int[] yCoords = mYCoordinates.getPrimitiveArray();
+        final int[] pointTypes = mPointTypes.getPrimitiveArray();
         final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase);
         int startIndex;
         for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
@@ -246,6 +255,17 @@
                         final int alpha = getAlpha(elapsedTime, params);
                         paint.setAlpha(alpha);
                         canvas.drawPath(path, paint);
+                        if (DBG_SHOW_POINTS) {
+                            if (pointTypes[i] == POINT_TYPE_INTERPOLATED) {
+                                paint.setColor(Color.RED);
+                            } else if (pointTypes[i] == POINT_TYPE_SAMPLED) {
+                                paint.setColor(0xFFA000FF);
+                            } else {
+                                paint.setColor(Color.GREEN);
+                            }
+                            canvas.drawCircle(p1x - 1, p1y - 1, 2, paint);
+                            paint.setColor(params.mTrailColor);
+                        }
                     }
                 }
                 p1x = p2x;
@@ -265,6 +285,9 @@
             mEventTimes.setLength(newSize);
             mXCoordinates.setLength(newSize);
             mYCoordinates.setLength(newSize);
+            if (DBG_SHOW_POINTS) {
+                mPointTypes.setLength(newSize);
+            }
             // The start index of the last segment of the stroke
             // {@link mLastInterpolatedDrawIndex} should also be updated because all array
             // elements have just been shifted for compaction or been zeroed.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 93ff264..70363e6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -145,7 +145,7 @@
     public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
         mKeyWidth = keyWidth;
         mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
-        mMaxYCoordinate = keyboardHeight - 1;
+        mMaxYCoordinate = keyboardHeight;
         // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
         mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold);
         mGestureDynamicDistanceThresholdFrom =
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index 7a51e25..ccb8802 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.content.res.TypedArray;
+
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResizableIntArray;
 
 public final class GestureStrokeWithPreviewPoints extends GestureStroke {
@@ -25,6 +28,8 @@
     private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
     private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
 
+    private final GestureStrokePreviewParams mPreviewParams;
+
     private int mStrokeId;
     private int mLastPreviewSize;
     private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
@@ -32,23 +37,53 @@
 
     private int mLastX;
     private int mLastY;
-    private double mMinPreviewSamplingDistance;
     private double mDistanceFromLastSample;
-    private double mInterpolationDistanceThreshold;
 
-    // TODO: Move these constants to resource.
-    // TODO: Use "dp" instead of ratio to the keyWidth because table has rather large keys.
-    // The minimum trail distance between sample points for preview in keyWidth unit when using
-    // interpolation.
-    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.2f;
-    // The angular threshold to use interpolation in radian. PI/12 is 15 degree.
-    private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d;
-    // The distance threshold to use interpolation in keyWidth unit.
-    private static final float INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH = 0.5f;
-    private static final int MAX_INTERPOLATION_PARTITIONS = 6;
+    public static final class GestureStrokePreviewParams {
+        public final double mMinSamplingDistance; // in pixel
+        public final double mMaxInterpolationAngularThreshold; // in radian
+        public final double mMaxInterpolationDistanceThreshold; // in pixel
+        public final int mMaxInterpolationSegments;
 
-    public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) {
-        super(pointerId, params);
+        public static final GestureStrokePreviewParams DEFAULT = new GestureStrokePreviewParams();
+
+        private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree
+
+        private GestureStrokePreviewParams() {
+            mMinSamplingDistance = 0.0d;
+            mMaxInterpolationAngularThreshold =
+                    degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD);
+            mMaxInterpolationDistanceThreshold = mMinSamplingDistance;
+            mMaxInterpolationSegments = 4;
+        }
+
+        private static double degreeToRadian(final int degree) {
+            return (double)degree / 180.0d * Math.PI;
+        }
+
+        public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
+            mMinSamplingDistance = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailMinSamplingDistance,
+                    (float)DEFAULT.mMinSamplingDistance);
+            final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable
+                    .MainKeyboardView_gesturePreviewTrailMaxInterpolationAngularThreshold, 0);
+            mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0)
+                    ? DEFAULT.mMaxInterpolationAngularThreshold
+                    : degreeToRadian(interpolationAngularDegree);
+            mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable
+                    .MainKeyboardView_gesturePreviewTrailMaxInterpolationDistanceThreshold,
+                    (float)DEFAULT.mMaxInterpolationDistanceThreshold);
+            mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailMaxInterpolationSegments,
+                    DEFAULT.mMaxInterpolationSegments);
+        }
+    }
+
+    public GestureStrokeWithPreviewPoints(final int pointerId,
+            final GestureStrokeParams strokeParams,
+            final GestureStrokePreviewParams previewParams) {
+        super(pointerId, strokeParams);
+        mPreviewParams = previewParams;
     }
 
     @Override
@@ -66,19 +101,12 @@
         return mStrokeId;
     }
 
-    @Override
-    public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
-        super.setKeyboardGeometry(keyWidth, keyboardHeight);
-        mMinPreviewSamplingDistance = keyWidth * MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH;
-        mInterpolationDistanceThreshold = keyWidth * INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH;
-    }
-
     private boolean needsSampling(final int x, final int y) {
         mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
         mLastX = x;
         mLastY = y;
         final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0);
-        if (mDistanceFromLastSample >= mMinPreviewSamplingDistance || isDownEvent) {
+        if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) {
             mDistanceFromLastSample = 0.0d;
             return true;
         }
@@ -124,7 +152,7 @@
      */
     public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
             final ResizableIntArray eventTimes, final ResizableIntArray xCoords,
-            final ResizableIntArray yCoords) {
+            final ResizableIntArray yCoords, final ResizableIntArray types) {
         final int size = mPreviewEventTimes.getLength();
         final int[] pt = mPreviewEventTimes.getPrimitiveArray();
         final int[] px = mPreviewXCoordinates.getPrimitiveArray();
@@ -144,28 +172,34 @@
             final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X);
             final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X);
             final double deltaAngle = Math.abs(angularDiff(m2, m1));
-            final int partitionsByAngle = (int)Math.ceil(
-                    deltaAngle / INTERPOLATION_ANGULAR_THRESHOLD);
+            final int segmentsByAngle = (int)Math.ceil(
+                    deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold);
             final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X,
                     mInterpolator.mP1Y - mInterpolator.mP2Y);
-            final int partitionsByDistance = (int)Math.ceil(deltaDistance
-                    / mInterpolationDistanceThreshold);
-            final int partitions = Math.min(MAX_INTERPOLATION_PARTITIONS,
-                    Math.max(partitionsByAngle, partitionsByDistance));
+            final int segmentsByDistance = (int)Math.ceil(deltaDistance
+                    / mPreviewParams.mMaxInterpolationDistanceThreshold);
+            final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments,
+                    Math.max(segmentsByAngle, segmentsByDistance));
             final int t1 = eventTimes.get(d1);
             final int dt = pt[p2] - pt[p1];
             d1++;
-            for (int i = 1; i < partitions; i++) {
-                final float t = i / (float)partitions;
+            for (int i = 1; i < segments; i++) {
+                final float t = i / (float)segments;
                 mInterpolator.interpolate(t);
                 eventTimes.add(d1, (int)(dt * t) + t1);
                 xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
                 yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
+                if (GesturePreviewTrail.DBG_SHOW_POINTS) {
+                    types.add(d1, GesturePreviewTrail.POINT_TYPE_INTERPOLATED);
+                }
                 d1++;
             }
             eventTimes.add(d1, pt[p2]);
             xCoords.add(d1, px[p2]);
             yCoords.add(d1, py[p2]);
+            if (GesturePreviewTrail.DBG_SHOW_POINTS) {
+                types.add(d1, GesturePreviewTrail.POINT_TYPE_SAMPLED);
+            }
         }
         return lastInterpolatedDrawIndex;
     }
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 75c2cf2..b9db9a0 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -16,21 +16,26 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.personalization.AccountUtils;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.SystemClock;
 import android.provider.BaseColumns;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.List;
 import java.util.Locale;
 
 public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
 
-    private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME,};
+    private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME};
     private static final String[] PROJECTION_ID_ONLY = {BaseColumns._ID};
 
     private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
@@ -102,9 +107,32 @@
 
     @Override
     public void loadDictionaryAsync() {
+        clearFusionDictionary();
+        loadDeviceAccountsEmailAddresses();
+        loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI);
+        // TODO: Switch this URL to the newer ContactsContract too
+        loadDictionaryAsyncForUri(Contacts.CONTENT_URI);
+    }
+
+    private void loadDeviceAccountsEmailAddresses() {
+        final List<String> accountVocabulary =
+                AccountUtils.getDeviceAccountsEmailAddresses(mContext);
+        if (accountVocabulary == null || accountVocabulary.isEmpty()) {
+            return;
+        }
+        for (String word : accountVocabulary) {
+            if (DEBUG) {
+                Log.d(TAG, "loadAccountVocabulary: " + word);
+            }
+            super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
+                    false /* isNotAWord */);
+        }
+    }
+
+    private void loadDictionaryAsyncForUri(final Uri uri) {
         try {
             Cursor cursor = mContext.getContentResolver()
-                    .query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
+                    .query(uri, PROJECTION, null, null, null);
             if (cursor != null) {
                 try {
                     if (cursor.moveToFirst()) {
@@ -129,7 +157,6 @@
     }
 
     private void addWords(final Cursor cursor) {
-        clearFusionDictionary();
         int count = 0;
         while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
             String name = cursor.getString(INDEX_NAME);
@@ -173,6 +200,9 @@
                 // capitalization of i.
                 final int wordLen = StringUtils.codePointCount(word);
                 if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
+                    if (DEBUG) {
+                        Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord);
+                    }
                     super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
                             false /* isNotAWord */);
                     if (!TextUtils.isEmpty(prevWord)) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 84c7529..fdd470c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -250,6 +250,7 @@
         }
 
         public void postResumeSuggestions() {
+            removeMessages(MSG_RESUME_SUGGESTIONS);
             sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
         }
 
@@ -759,7 +760,8 @@
         }
         mSuggestedWords = SuggestedWords.EMPTY;
 
-        mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart);
+        mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart,
+                false /* shouldFinishComposition */);
 
         if (isDifferentTextField) {
             mainKeyboardView.closing();
@@ -1148,13 +1150,14 @@
     // This will reset the whole input state to the starting state. It will clear
     // the composing word, reset the last composed word, tell the inputconnection about it.
     private void resetEntireInputState(final int newCursorPosition) {
+        final boolean shouldFinishComposition = mWordComposer.isComposingWord();
         resetComposingState(true /* alsoResetLastComposedWord */);
         if (mSettings.getCurrent().mBigramPredictionEnabled) {
             clearSuggestionStrip();
         } else {
             setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
         }
-        mConnection.resetCachesUponCursorMove(newCursorPosition);
+        mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
     }
 
     private void resetComposingState(final boolean alsoResetLastComposedWord) {
@@ -2436,10 +2439,15 @@
     private void restartSuggestionsOnWordTouchedByCursor() {
         // If the cursor is not touching a word, or if there is a selection, return right away.
         if (mLastSelectionStart != mLastSelectionEnd) return;
+        // If we don't know the cursor location, return.
+        if (mLastSelectionStart < 0) return;
         if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
         final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
                 0 /* additionalPrecedingWordsCount */);
         if (null == range) return; // Happens if we don't have an input connection at all
+        // If for some strange reason (editor bug or so) we measure the text before the cursor as
+        // longer than what the entire text is supposed to be, the safe thing to do is bail out.
+        if (range.mCharsBefore > mLastSelectionStart) return;
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         final String typedWord = range.mWord.toString();
         if (range.mWord instanceof SpannableString) {
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
index b74b979..f0bfe75 100644
--- a/java/src/com/android/inputmethod/latin/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -35,8 +35,7 @@
         // This utility class is not publicly instantiable.
     }
 
-    private static final String DEFAULT_PREFIX = "DEFAULT,";
-    private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
+    private static final String DEFAULT_KEY = "DEFAULT";
     private static final HashMap<String, String> sDeviceOverrideValueMap =
             CollectionUtils.newHashMap();
 
@@ -48,28 +47,29 @@
         }
 
         final String[] overrideArray = res.getStringArray(overrideResId);
-        final String overrideValue = StringUtils.findPrefixedString(HARDWARE_PREFIX, overrideArray);
+        final String hardwareKey = "HARDWARE=" + Build.HARDWARE;
+        final String overrideValue = StringUtils.findValueOfKey(hardwareKey, overrideArray);
         // The overrideValue might be an empty string.
         if (overrideValue != null) {
             if (DEBUG) {
                 Log.d(TAG, "Find override value:"
                         + " resource="+ res.getResourceEntryName(overrideResId)
-                        + " Build.HARDWARE=" + Build.HARDWARE + " override=" + overrideValue);
+                        + " " + hardwareKey + " override=" + overrideValue);
             }
             sDeviceOverrideValueMap.put(key, overrideValue);
             return overrideValue;
         }
 
-        final String defaultValue = StringUtils.findPrefixedString(DEFAULT_PREFIX, overrideArray);
+        final String defaultValue = StringUtils.findValueOfKey(DEFAULT_KEY, overrideArray);
         // The defaultValue might be an empty string.
         if (defaultValue == null) {
             Log.w(TAG, "Couldn't find override value nor default value:"
                     + " resource="+ res.getResourceEntryName(overrideResId)
-                    + " Build.HARDWARE=" + Build.HARDWARE);
+                    + " " + hardwareKey);
         } else if (DEBUG) {
             Log.d(TAG, "Found default value:"
                 + " resource="+ res.getResourceEntryName(overrideResId)
-                + " Build.HARDWARE=" + Build.HARDWARE + " default=" + defaultValue);
+                + " " + hardwareKey + " " + DEFAULT_KEY + "=" + defaultValue);
         }
         sDeviceOverrideValueMap.put(key, defaultValue);
         return defaultValue;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8ed7ab2..980215d 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -135,13 +135,14 @@
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
     }
 
-    public void resetCachesUponCursorMove(final int newCursorPosition) {
+    public void resetCachesUponCursorMove(final int newCursorPosition,
+            final boolean shouldFinishComposition) {
         mCurrentCursorPosition = newCursorPosition;
         mComposingText.setLength(0);
         mCommittedTextBeforeComposingText.setLength(0);
         final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
         if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor);
-        if (null != mIC) {
+        if (null != mIC && shouldFinishComposition) {
             mIC.finishComposingText();
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.richInputConnection_finishComposingText();
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 3f7be99..94513e6 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -100,6 +100,12 @@
         throw new RuntimeException("Input method id for " + packageName + " not found.");
     }
 
+    public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
+            boolean allowsImplicitlySelectedSubtypes) {
+        return mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
+                mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes);
+    }
+
     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
         if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
             return true;
@@ -116,8 +122,8 @@
             final boolean onlyCurrentIme) {
         final InputMethodManager imm = mImmWrapper.mImm;
         final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
-        final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList(
-                mInputMethodInfoOfThisIme, true /* allowsImplicitlySelectedSubtypes */);
+        final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(
+                true /* allowsImplicitlySelectedSubtypes */);
         final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
         if (currentIndex == INDEX_NOT_FOUND) {
             Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
@@ -214,8 +220,8 @@
             final InputMethodSubtype subtype) {
         final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
         final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
-                subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
-                        mInputMethodInfoOfThisIme, false /* allowsImplicitlySelectedSubtypes */));
+                subtype, getMyEnabledInputMethodSubtypeList(
+                        false /* allowsImplicitlySelectedSubtypes */));
         return subtypeEnabled && !subtypeExplicitlyEnabled;
     }
 
@@ -312,8 +318,7 @@
         if (filteredImisCount > 1) {
             return true;
         }
-        final List<InputMethodSubtype> subtypes =
-                mImmWrapper.mImm.getEnabledInputMethodSubtypeList(null, true);
+        final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true);
         int keyboardCount = 0;
         // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
         // both explicitly and implicitly enabled input method subtype.
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index d5ee58a..5ff101f 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -65,17 +65,24 @@
     }
 
     /**
-     * Find a string that start with specified prefix from an array.
+     * Find a value that has a specified key from an array of key-comma-value.
      *
-     * @param prefix a prefix string to find.
-     * @param array an string array to be searched.
-     * @return the rest part of the string that starts with the prefix.
+     * @param key a key string to find.
+     * @param array an array of key-comma-value string to be searched.
+     * @return the value part of the first string that has a specified key.
      * Returns null if it couldn't be found.
      */
-    public static String findPrefixedString(final String prefix, final String[] array) {
+    public static String findValueOfKey(final String key, final String[] array) {
+        if (array == null) {
+            return null;
+        }
         for (final String element : array) {
-            if (element.startsWith(prefix)) {
-                return element.substring(prefix.length());
+            final int posComma = element.indexOf(',');
+            if (posComma < 0) {
+                throw new RuntimeException("Element has no comma: " + element);
+            }
+            if (element.substring(0, posComma).equals(key)) {
+                return element.substring(posComma + 1);
             }
         }
         return null;
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index bef8a3c..282b579 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -115,7 +115,7 @@
      */
     public void updateParametersOnStartInputView() {
         final List<InputMethodSubtype> enabledSubtypesOfThisIme =
-                mRichImm.getInputMethodManager().getEnabledInputMethodSubtypeList(null, true);
+                mRichImm.getMyEnabledInputMethodSubtypeList(true);
         mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
         updateShortcutIME();
     }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 51bd901..e078f03 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 
@@ -211,9 +210,8 @@
     }
 
     /**
-     * Internal method to retrieve reasonable proximity info for a character.
+     * Add a dummy key by retrieving reasonable coordinates
      */
-    @UsedForTesting
     public void addKeyInfo(final int codePoint, final Keyboard keyboard) {
         final int x, y;
         final Key key;
diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
new file mode 100644
index 0000000..93687e1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.personalization;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.util.Patterns;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AccountUtils {
+    private AccountUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    private static Account[] getAccounts(final Context context) {
+        return AccountManager.get(context).getAccounts();
+    }
+
+    public static List<String> getDeviceAccountsEmailAddresses(final Context context) {
+        final ArrayList<String> retval = new ArrayList<String>();
+        for (final Account account : getAccounts(context)) {
+            final String name = account.name;
+            if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) {
+                retval.add(name);
+                retval.add(name.split("@")[0]);
+            }
+        }
+        return retval;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index affe3a3..8a2de88 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -17,270 +17,27 @@
 package com.android.inputmethod.latin.setup;
 
 import android.app.Activity;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
-import android.media.MediaPlayer;
-import android.net.Uri;
 import android.os.Bundle;
-import android.os.Message;
 import android.provider.Settings;
-import android.util.Log;
-import android.view.View;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.VideoView;
 
-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.ArrayList;
-
-// TODO: Use Fragment to implement welcome screen and setup steps.
-public final class SetupActivity extends Activity implements View.OnClickListener {
-    private static final String TAG = SetupActivity.class.getSimpleName();
-
-    private View mWelcomeScreen;
-    private View mSetupScreen;
-    private Uri mWelcomeVideoUri;
-    private VideoView mWelcomeVideoView;
-    private View mActionStart;
-    private View mActionNext;
-    private TextView mStep1Bullet;
-    private TextView mActionFinish;
-    private SetupStepGroup mSetupStepGroup;
-    private static final String STATE_STEP = "step";
-    private int mStepNumber;
-    private static final int STEP_WELCOME = 0;
-    private static final int STEP_1 = 1;
-    private static final int STEP_2 = 2;
-    private static final int STEP_3 = 3;
-    private boolean mWasLanguageAndInputSettingsInvoked;
-
-    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();
-            if (setupActivity == null) {
-                return;
-            }
-            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);
-        }
-    }
-
+public final class SetupActivity extends Activity {
     @Override
     protected void onCreate(final Bundle savedInstanceState) {
-        setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar);
         super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.setup_wizard);
-
-        RichInputMethodManager.init(this);
-
-        if (savedInstanceState == null) {
-            mStepNumber = determineSetupStepNumber();
-            if (mStepNumber == STEP_1 && !mWasLanguageAndInputSettingsInvoked) {
-                mStepNumber = STEP_WELCOME;
-            }
-            if (mStepNumber == STEP_3) {
-                // This IME already has been enabled and set as current IME.
-                // TODO: Implement tutorial.
-                invokeSettingsOfThisIme();
-                finish();
-                return;
-            }
-        } else {
-            mStepNumber = savedInstanceState.getInt(STATE_STEP);
-        }
-
-        final String applicationName = getResources().getString(getApplicationInfo().labelRes);
-        mWelcomeScreen = findViewById(R.id.setup_welcome_screen);
-        final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title);
-        welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName));
-
-        mSetupScreen = findViewById(R.id.setup_steps_screen);
-        final TextView stepsTitle = (TextView)findViewById(R.id.setup_title);
-        stepsTitle.setText(getString(R.string.setup_steps_title, applicationName));
-
-        final SetupStepIndicatorView indicatorView =
-                (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator);
-        mSetupStepGroup = new SetupStepGroup(indicatorView);
-
-        mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet);
-        mStep1Bullet.setOnClickListener(this);
-        final SetupStep step1 = new SetupStep(STEP_1, applicationName,
-                mStep1Bullet, findViewById(R.id.setup_step1),
-                R.string.setup_step1_title, R.string.setup_step1_instruction,
-                R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1,
-                R.string.setup_step1_action);
-        step1.setAction(new Runnable() {
-            @Override
-            public void run() {
-                invokeLanguageAndInputSettings();
-                mHandler.startPollingImeSettings();
-            }
-        });
-        mSetupStepGroup.addStep(step1);
-
-        final SetupStep step2 = new SetupStep(STEP_2, applicationName,
-                (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2),
-                R.string.setup_step2_title, R.string.setup_step2_instruction,
-                0 /* finishedInstruction */, R.drawable.ic_setup_step2,
-                R.string.setup_step2_action);
-        step2.setAction(new Runnable() {
-            @Override
-            public void run() {
-                // Invoke input method picker.
-                RichInputMethodManager.getInstance().getInputMethodManager()
-                        .showInputMethodPicker();
-            }
-        });
-        mSetupStepGroup.addStep(step2);
-
-        final SetupStep step3 = new SetupStep(STEP_3, applicationName,
-                (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3),
-                R.string.setup_step3_title, R.string.setup_step3_instruction,
-                0 /* finishedInstruction */, R.drawable.ic_setup_step3,
-                R.string.setup_step3_action);
-        step3.setAction(new Runnable() {
-            @Override
-            public void run() {
-                invokeSubtypeEnablerOfThisIme();
-            }
-        });
-        mSetupStepGroup.addStep(step3);
-
-        mWelcomeVideoUri = new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
-                .authority(getPackageName())
-                .path(Integer.toString(R.raw.setup_welcome_video))
-                .build();
-        mWelcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video);
-        mWelcomeVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(final MediaPlayer mp) {
-                mp.start();
-            }
-        });
-        mWelcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-            @Override
-            public void onPrepared(final MediaPlayer mp) {
-                // Now VideoView has been laid-out and ready to play, remove background of it to
-                // reveal the video.
-                mWelcomeVideoView.setBackgroundResource(0);
-            }
-        });
-        final ImageView welcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image);
-        mWelcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(final MediaPlayer mp, final int what, final int extra) {
-                Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra);
-                mWelcomeVideoView.setVisibility(View.GONE);
-                welcomeImageView.setImageResource(R.raw.setup_welcome_image);
-                welcomeImageView.setVisibility(View.VISIBLE);
-                return true;
-            }
-        });
-
-        mActionStart = findViewById(R.id.setup_start_label);
-        mActionStart.setOnClickListener(this);
-        mActionNext = findViewById(R.id.setup_next);
-        mActionNext.setOnClickListener(this);
-        mActionFinish = (TextView)findViewById(R.id.setup_finish);
-        TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish,
-                getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null);
-        mActionFinish.setOnClickListener(this);
-    }
-
-    @Override
-    public void onClick(final View v) {
-        if (v == mActionFinish) {
+        final Intent intent = new Intent();
+        intent.setClass(this, SetupWizardActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+                | Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+        if (!isFinishing()) {
             finish();
-            return;
         }
-        final int currentStep = determineSetupStepNumber();
-        final int nextStep;
-        if (v == mActionStart) {
-            nextStep = STEP_1;
-        } else if (v == mActionNext) {
-            nextStep = mStepNumber + 1;
-        } else if (v == mStep1Bullet && currentStep == STEP_2) {
-            nextStep = STEP_1;
-        } else {
-            nextStep = mStepNumber;
-        }
-        if (mStepNumber != nextStep) {
-            mStepNumber = nextStep;
-            updateSetupStepView();
-        }
-    }
-
-    private void invokeSetupWizardOfThisIme() {
-        final Intent intent = new Intent();
-        intent.setClass(this, SetupActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        startActivity(intent);
-    }
-
-    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);
-        mWasLanguageAndInputSettingsInvoked = true;
-    }
-
-    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);
     }
 
     /**
@@ -317,164 +74,4 @@
                 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 onRestart() {
-        super.onRestart();
-        if (mStepNumber != STEP_WELCOME) {
-            mStepNumber = determineSetupStepNumber();
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        updateSetupStepView();
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (mStepNumber == STEP_1) {
-            mStepNumber = STEP_WELCOME;
-            updateSetupStepView();
-            return;
-        }
-        super.onBackPressed();
-    }
-
-    @Override
-    protected void onPause() {
-        mWelcomeVideoView.stopPlayback();
-        super.onPause();
-    }
-
-    @Override
-    public void onWindowFocusChanged(final boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        if (hasFocus && mStepNumber != STEP_WELCOME) {
-            mStepNumber = determineSetupStepNumber();
-            updateSetupStepView();
-        }
-    }
-
-    private void updateSetupStepView() {
-        final boolean welcomeScreen = (mStepNumber == STEP_WELCOME);
-        mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE);
-        mSetupScreen.setVisibility(welcomeScreen ? View.GONE: View.VISIBLE);
-        if (welcomeScreen) {
-            mWelcomeVideoView.setVideoURI(mWelcomeVideoUri);
-            mWelcomeVideoView.start();
-            return;
-        }
-        mWelcomeVideoView.stopPlayback();
-        final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber();
-        mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone);
-        mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE);
-        mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE);
-    }
-
-    static final class SetupStep implements View.OnClickListener {
-        public final int mStepNo;
-        private final View mStepView;
-        private final TextView mBulletView;
-        private final int mActivatedColor;
-        private final int mDeactivatedColor;
-        private final String mInstruction;
-        private final String mFinishedInstruction;
-        private final TextView mActionLabel;
-        private Runnable mAction;
-
-        public SetupStep(final int stepNo, final String applicationName, final TextView bulletView,
-                final View stepView, final int title, final int instruction,
-                final int finishedInstruction,final int actionIcon, final int actionLabel) {
-            mStepNo = stepNo;
-            mStepView = stepView;
-            mBulletView = bulletView;
-            final Resources res = stepView.getResources();
-            mActivatedColor = res.getColor(R.color.setup_text_action);
-            mDeactivatedColor = res.getColor(R.color.setup_text_dark);
-
-            final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title);
-            titleView.setText(res.getString(title, applicationName));
-            mInstruction = (instruction == 0) ? null
-                    : res.getString(instruction, applicationName);
-            mFinishedInstruction = (finishedInstruction == 0) ? null
-                    : res.getString(finishedInstruction, applicationName);
-
-            mActionLabel = (TextView)mStepView.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 {
-                TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                        mActionLabel, res.getDrawable(actionIcon), null, null, null);
-            }
-        }
-
-        public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) {
-            mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE);
-            mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor);
-            final TextView instructionView = (TextView)mStepView.findViewById(
-                    R.id.setup_step_instruction);
-            instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction);
-            mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE);
-        }
-
-        public void setAction(final Runnable action) {
-            mActionLabel.setOnClickListener(this);
-            mAction = action;
-        }
-
-        @Override
-        public void onClick(final View v) {
-            if (v == mActionLabel && mAction != null) {
-                mAction.run();
-                return;
-            }
-        }
-    }
-
-    static final class SetupStepGroup {
-        private final SetupStepIndicatorView mIndicatorView;
-        private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList();
-
-        public SetupStepGroup(final SetupStepIndicatorView indicatorView) {
-            mIndicatorView = indicatorView;
-        }
-
-        public void addStep(final SetupStep step) {
-            mGroup.add(step);
-        }
-
-        public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) {
-            for (final SetupStep step : mGroup) {
-                step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone);
-            }
-            mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size());
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
new file mode 100644
index 0000000..3406ecf
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -0,0 +1,466 @@
+/*
+ * 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.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.inputmethod.InputMethodInfo;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.VideoView;
+
+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.ArrayList;
+
+// TODO: Use Fragment to implement welcome screen and setup steps.
+public final class SetupWizardActivity extends Activity implements View.OnClickListener {
+    static final String TAG = SetupWizardActivity.class.getSimpleName();
+
+    private View mSetupWizard;
+    private View mWelcomeScreen;
+    private View mSetupScreen;
+    private Uri mWelcomeVideoUri;
+    private VideoView mWelcomeVideoView;
+    private View mActionStart;
+    private View mActionNext;
+    private TextView mStep1Bullet;
+    private TextView mActionFinish;
+    private SetupStepGroup mSetupStepGroup;
+    private static final String STATE_STEP = "step";
+    private int mStepNumber;
+    private static final int STEP_WELCOME = 0;
+    private static final int STEP_1 = 1;
+    private static final int STEP_2 = 2;
+    private static final int STEP_3 = 3;
+    private static final int STEP_LAUNCHING_IME_SETTINGS = 4;
+    private static final int STEP_BACK_FROM_IME_SETTINGS = 5;
+
+    final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this);
+
+    static final class SettingsPoolingHandler
+            extends StaticInnerHandlerWrapper<SetupWizardActivity> {
+        private static final int MSG_POLLING_IME_SETTINGS = 0;
+        private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
+
+        public SettingsPoolingHandler(final SetupWizardActivity outerInstance) {
+            super(outerInstance);
+        }
+
+        @Override
+        public void handleMessage(final Message msg) {
+            final SetupWizardActivity setupWizardActivity = getOuterInstance();
+            if (setupWizardActivity == null) {
+                return;
+            }
+            switch (msg.what) {
+            case MSG_POLLING_IME_SETTINGS:
+                if (SetupActivity.isThisImeEnabled(setupWizardActivity)) {
+                    setupWizardActivity.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(final Bundle savedInstanceState) {
+        setTheme(android.R.style.Theme_Translucent_NoTitleBar);
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.setup_wizard);
+        mSetupWizard = findViewById(R.id.setup_wizard);
+
+        RichInputMethodManager.init(this);
+
+        if (savedInstanceState == null) {
+            mStepNumber = determineSetupStepNumberFromLauncher();
+        } else {
+            mStepNumber = savedInstanceState.getInt(STATE_STEP);
+        }
+
+        final String applicationName = getResources().getString(getApplicationInfo().labelRes);
+        mWelcomeScreen = findViewById(R.id.setup_welcome_screen);
+        final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title);
+        welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName));
+
+        mSetupScreen = findViewById(R.id.setup_steps_screen);
+        final TextView stepsTitle = (TextView)findViewById(R.id.setup_title);
+        stepsTitle.setText(getString(R.string.setup_steps_title, applicationName));
+
+        final SetupStepIndicatorView indicatorView =
+                (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator);
+        mSetupStepGroup = new SetupStepGroup(indicatorView);
+
+        mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet);
+        mStep1Bullet.setOnClickListener(this);
+        final SetupStep step1 = new SetupStep(STEP_1, applicationName,
+                mStep1Bullet, findViewById(R.id.setup_step1),
+                R.string.setup_step1_title, R.string.setup_step1_instruction,
+                R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1,
+                R.string.setup_step1_action);
+        step1.setAction(new Runnable() {
+            @Override
+            public void run() {
+                invokeLanguageAndInputSettings();
+                mHandler.startPollingImeSettings();
+            }
+        });
+        mSetupStepGroup.addStep(step1);
+
+        final SetupStep step2 = new SetupStep(STEP_2, applicationName,
+                (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2),
+                R.string.setup_step2_title, R.string.setup_step2_instruction,
+                0 /* finishedInstruction */, R.drawable.ic_setup_step2,
+                R.string.setup_step2_action);
+        step2.setAction(new Runnable() {
+            @Override
+            public void run() {
+                // Invoke input method picker.
+                RichInputMethodManager.getInstance().getInputMethodManager()
+                        .showInputMethodPicker();
+            }
+        });
+        mSetupStepGroup.addStep(step2);
+
+        final SetupStep step3 = new SetupStep(STEP_3, applicationName,
+                (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3),
+                R.string.setup_step3_title, R.string.setup_step3_instruction,
+                0 /* finishedInstruction */, R.drawable.ic_setup_step3,
+                R.string.setup_step3_action);
+        step3.setAction(new Runnable() {
+            @Override
+            public void run() {
+                invokeSubtypeEnablerOfThisIme();
+            }
+        });
+        mSetupStepGroup.addStep(step3);
+
+        mWelcomeVideoUri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                .authority(getPackageName())
+                .path(Integer.toString(R.raw.setup_welcome_video))
+                .build();
+        final VideoView welcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video);
+        welcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(final MediaPlayer mp) {
+                // Now VideoView has been laid-out and ready to play, remove background of it to
+                // reveal the video.
+                welcomeVideoView.setBackgroundResource(0);
+                mp.setLooping(true);
+            }
+        });
+        final ImageView welcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image);
+        welcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(final MediaPlayer mp, final int what, final int extra) {
+                Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra);
+                welcomeVideoView.setVisibility(View.GONE);
+                welcomeImageView.setImageResource(R.raw.setup_welcome_image);
+                welcomeImageView.setVisibility(View.VISIBLE);
+                return true;
+            }
+        });
+        mWelcomeVideoView = welcomeVideoView;
+
+        mActionStart = findViewById(R.id.setup_start_label);
+        mActionStart.setOnClickListener(this);
+        mActionNext = findViewById(R.id.setup_next);
+        mActionNext.setOnClickListener(this);
+        mActionFinish = (TextView)findViewById(R.id.setup_finish);
+        TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish,
+                getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null);
+        mActionFinish.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(final View v) {
+        if (v == mActionFinish) {
+            finish();
+            return;
+        }
+        final int currentStep = determineSetupStepNumber();
+        final int nextStep;
+        if (v == mActionStart) {
+            nextStep = STEP_1;
+        } else if (v == mActionNext) {
+            nextStep = mStepNumber + 1;
+        } else if (v == mStep1Bullet && currentStep == STEP_2) {
+            nextStep = STEP_1;
+        } else {
+            nextStep = mStepNumber;
+        }
+        if (mStepNumber != nextStep) {
+            mStepNumber = nextStep;
+            updateSetupStepView();
+        }
+    }
+
+    void invokeSetupWizardOfThisIme() {
+        final Intent intent = new Intent();
+        intent.setClass(this, SetupWizardActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                | Intent.FLAG_ACTIVITY_SINGLE_TOP
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        startActivity(intent);
+    }
+
+    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);
+    }
+
+    void invokeLanguageAndInputSettings() {
+        final Intent intent = new Intent();
+        intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        startActivity(intent);
+    }
+
+    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);
+    }
+
+    private int determineSetupStepNumberFromLauncher() {
+        final int stepNumber = determineSetupStepNumber();
+        if (stepNumber == STEP_1) {
+            return STEP_WELCOME;
+        }
+        if (stepNumber == STEP_3) {
+            return STEP_LAUNCHING_IME_SETTINGS;
+        }
+        return stepNumber;
+    }
+
+    private int determineSetupStepNumber() {
+        mHandler.cancelPollingImeSettings();
+        if (!SetupActivity.isThisImeEnabled(this)) {
+            return STEP_1;
+        }
+        if (!SetupActivity.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);
+    }
+
+    private static boolean isInSetupSteps(final int stepNumber) {
+        return stepNumber >= STEP_1 && stepNumber <= STEP_3;
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        if (isInSetupSteps(mStepNumber)) {
+            mStepNumber = determineSetupStepNumber();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mStepNumber == STEP_LAUNCHING_IME_SETTINGS) {
+            // Prevent white screen flashing while launching settings activity.
+            mSetupWizard.setVisibility(View.INVISIBLE);
+            invokeSettingsOfThisIme();
+            mStepNumber = STEP_BACK_FROM_IME_SETTINGS;
+            return;
+        }
+        if (mStepNumber == STEP_BACK_FROM_IME_SETTINGS) {
+            finish();
+            return;
+        }
+        updateSetupStepView();
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mStepNumber == STEP_1) {
+            mStepNumber = STEP_WELCOME;
+            updateSetupStepView();
+            return;
+        }
+        super.onBackPressed();
+    }
+
+    private static void hideAndStopVideo(final VideoView videoView) {
+        videoView.stopPlayback();
+        videoView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    protected void onPause() {
+        hideAndStopVideo(mWelcomeVideoView);
+        super.onPause();
+    }
+
+    @Override
+    public void onWindowFocusChanged(final boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (hasFocus && isInSetupSteps(mStepNumber)) {
+            mStepNumber = determineSetupStepNumber();
+            updateSetupStepView();
+        }
+    }
+
+    private void updateSetupStepView() {
+        mSetupWizard.setVisibility(View.VISIBLE);
+        final boolean welcomeScreen = (mStepNumber == STEP_WELCOME);
+        mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE);
+        mSetupScreen.setVisibility(welcomeScreen ? View.GONE : View.VISIBLE);
+        if (welcomeScreen) {
+            mWelcomeVideoView.setVisibility(View.VISIBLE);
+            mWelcomeVideoView.setVideoURI(mWelcomeVideoUri);
+            mWelcomeVideoView.start();
+            return;
+        }
+        hideAndStopVideo(mWelcomeVideoView);
+        final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber();
+        mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone);
+        mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE);
+        mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE);
+    }
+
+    static final class SetupStep implements View.OnClickListener {
+        public final int mStepNo;
+        private final View mStepView;
+        private final TextView mBulletView;
+        private final int mActivatedColor;
+        private final int mDeactivatedColor;
+        private final String mInstruction;
+        private final String mFinishedInstruction;
+        private final TextView mActionLabel;
+        private Runnable mAction;
+
+        public SetupStep(final int stepNo, final String applicationName, final TextView bulletView,
+                final View stepView, final int title, final int instruction,
+                final int finishedInstruction, final int actionIcon, final int actionLabel) {
+            mStepNo = stepNo;
+            mStepView = stepView;
+            mBulletView = bulletView;
+            final Resources res = stepView.getResources();
+            mActivatedColor = res.getColor(R.color.setup_text_action);
+            mDeactivatedColor = res.getColor(R.color.setup_text_dark);
+
+            final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title);
+            titleView.setText(res.getString(title, applicationName));
+            mInstruction = (instruction == 0) ? null
+                    : res.getString(instruction, applicationName);
+            mFinishedInstruction = (finishedInstruction == 0) ? null
+                    : res.getString(finishedInstruction, applicationName);
+
+            mActionLabel = (TextView)mStepView.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 {
+                TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                        mActionLabel, res.getDrawable(actionIcon), null, null, null);
+            }
+        }
+
+        public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) {
+            mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE);
+            mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor);
+            final TextView instructionView = (TextView)mStepView.findViewById(
+                    R.id.setup_step_instruction);
+            instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction);
+            mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE);
+        }
+
+        public void setAction(final Runnable action) {
+            mActionLabel.setOnClickListener(this);
+            mAction = action;
+        }
+
+        @Override
+        public void onClick(final View v) {
+            if (v == mActionLabel && mAction != null) {
+                mAction.run();
+                return;
+            }
+        }
+    }
+
+    static final class SetupStepGroup {
+        private final SetupStepIndicatorView mIndicatorView;
+        private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList();
+
+        public SetupStepGroup(final SetupStepIndicatorView indicatorView) {
+            mIndicatorView = indicatorView;
+        }
+
+        public void addStep(final SetupStep step) {
+            mGroup.add(step);
+        }
+
+        public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) {
+            for (final SetupStep step : mGroup) {
+                step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone);
+            }
+            mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size());
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index aa60496..13fcaf4 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -23,7 +23,7 @@
 import android.util.Log;
 import android.view.textservice.SuggestionsInfo;
 
-import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
 import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.ContactsBinaryDictionary;
@@ -126,6 +126,19 @@
         return script;
     }
 
+    private static String getKeyboardLayoutNameForScript(final int script) {
+        switch (script) {
+        case AndroidSpellCheckerService.SCRIPT_LATIN:
+            return "qwerty";
+        case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+            return "east_slavic";
+        case AndroidSpellCheckerService.SCRIPT_GREEK:
+            return "greek";
+        default:
+            throw new RuntimeException("Wrong script supplied: " + script);
+        }
+    }
+
     @Override
     public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
         if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
@@ -385,9 +398,13 @@
         return pool;
     }
 
-    public DictAndProximity createDictAndProximity(final Locale locale) {
+    public DictAndKeyboard createDictAndKeyboard(final Locale locale) {
         final int script = getScriptFromLocale(locale);
-        final ProximityInfo proximityInfo = new SpellCheckerProximityInfo(script);
+        final String keyboardLayoutName = getKeyboardLayoutNameForScript(script);
+        final KeyboardLayoutSet keyboardLayoutSet =
+                KeyboardLayoutSet.createKeyboardSetForSpellChecker(this, locale.toString(),
+                        keyboardLayoutName);
+
         final DictionaryCollection dictionaryCollection =
                 DictionaryFactory.createMainDictionaryFromManager(this, locale,
                         true /* useFullEditDistance */);
@@ -412,6 +429,6 @@
             mDictionaryCollectionsList.add(
                     new WeakReference<DictionaryCollection>(dictionaryCollection));
         }
-        return new DictAndProximity(dictionaryCollection, proximityInfo);
+        return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 61850e4..16e9fb7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -257,7 +257,7 @@
             }
 
             if (shouldFilterOut(inText, mScript)) {
-                DictAndProximity dictInfo = null;
+                DictAndKeyboard dictInfo = null;
                 try {
                     dictInfo = mDictionaryPool.pollWithDefaultTimeout();
                     if (!DictionaryPool.isAValidDictionary(dictInfo)) {
@@ -286,7 +286,7 @@
 
             final int capitalizeType = StringUtils.getCapitalizationType(text);
             boolean isInDict = true;
-            DictAndProximity dictInfo = null;
+            DictAndKeyboard dictInfo = null;
             try {
                 dictInfo = mDictionaryPool.pollWithDefaultTimeout();
                 if (!DictionaryPool.isAValidDictionary(dictInfo)) {
@@ -296,20 +296,13 @@
                 final int length = text.length();
                 for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
                     final int codePoint = text.codePointAt(i);
-                    // The getXYForCodePointAndScript method returns (Y << 16) + X
-                    final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
-                            codePoint, mScript);
-                    if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
-                        composer.add(codePoint,
-                                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-                    } else {
-                        composer.add(codePoint, xy & 0xFFFF, xy >> 16);
-                    }
+                    composer.addKeyInfo(codePoint, dictInfo.getKeyboard(codePoint));
                 }
                 // TODO: make a spell checker option to block offensive words or not
                 final ArrayList<SuggestedWordInfo> suggestions =
                         dictInfo.mDictionary.getSuggestions(composer, prevWord,
-                                dictInfo.mProximityInfo, true /* blockOffensiveWords */);
+                                dictInfo.getProximityInfo(),
+                                true /* blockOffensiveWords */);
                 for (final SuggestedWordInfo suggestion : suggestions) {
                     final String suggestionStr = suggestion.mWord;
                     suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
new file mode 100644
index 0000000..b77f3e2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.spellcheck;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
+/**
+ * A container for a Dictionary and a Keyboard.
+ */
+public final class DictAndKeyboard {
+    public final Dictionary mDictionary;
+    private final Keyboard mKeyboard;
+    private final Keyboard mManualShiftedKeyboard;
+
+    public DictAndKeyboard(
+            final Dictionary dictionary, final KeyboardLayoutSet keyboardLayoutSet) {
+        mDictionary = dictionary;
+        if (keyboardLayoutSet == null) {
+            mKeyboard = null;
+            mManualShiftedKeyboard = null;
+            return;
+        }
+        mKeyboard = keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
+        mManualShiftedKeyboard =
+                keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
+    }
+
+    public Keyboard getKeyboard(final int codePoint) {
+        if (mKeyboard == null) {
+            return null;
+        }
+        return mKeyboard.getKey(codePoint) != null ? mKeyboard : mManualShiftedKeyboard;
+    }
+
+    public ProximityInfo getProximityInfo() {
+        return mKeyboard == null ? null : mKeyboard.getProximityInfo();
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
deleted file mode 100644
index 017a4f5..0000000
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.spellcheck;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-/**
- * A simple container for both a Dictionary and a ProximityInfo.
- */
-public final class DictAndProximity {
-    public final Dictionary mDictionary;
-    public final ProximityInfo mProximityInfo;
-    public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) {
-        mDictionary = dictionary;
-        mProximityInfo = proximityInfo;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 27964b3..a20e09e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -36,7 +36,7 @@
  * the client code, but may help with sloppy clients.
  */
 @SuppressWarnings("serial")
-public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
     private final static String TAG = DictionaryPool.class.getSimpleName();
     // How many seconds we wait for a dictionary to become available. Past this delay, we give up in
     // fear some bug caused a deadlock, and reset the whole pool.
@@ -47,7 +47,7 @@
     private int mSize;
     private volatile boolean mClosed;
     final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
-    private final static DictAndProximity dummyDict = new DictAndProximity(
+    private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
             new Dictionary(Dictionary.TYPE_MAIN) {
                 @Override
                 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
@@ -64,7 +64,7 @@
                 }
             }, null);
 
-    static public boolean isAValidDictionary(final DictAndProximity dictInfo) {
+    static public boolean isAValidDictionary(final DictAndKeyboard dictInfo) {
         return null != dictInfo && dummyDict != dictInfo;
     }
 
@@ -79,32 +79,32 @@
     }
 
     @Override
-    public DictAndProximity poll(final long timeout, final TimeUnit unit)
+    public DictAndKeyboard poll(final long timeout, final TimeUnit unit)
             throws InterruptedException {
-        final DictAndProximity dict = poll();
+        final DictAndKeyboard dict = poll();
         if (null != dict) return dict;
         synchronized(this) {
             if (mSize >= mMaxSize) {
                 // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
                 // expires to avoid a deadlock.
-                final DictAndProximity result = super.poll(timeout, unit);
+                final DictAndKeyboard result = super.poll(timeout, unit);
                 if (null == result) {
                     Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
                     clear();
                     mSize = 1;
-                    return mService.createDictAndProximity(mLocale);
+                    return mService.createDictAndKeyboard(mLocale);
                 } else {
                     return result;
                 }
             } else {
                 ++mSize;
-                return mService.createDictAndProximity(mLocale);
+                return mService.createDictAndKeyboard(mLocale);
             }
         }
     }
 
     // Convenience method
-    public DictAndProximity pollWithDefaultTimeout() {
+    public DictAndKeyboard pollWithDefaultTimeout() {
         try {
             return poll(TIMEOUT, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
@@ -115,7 +115,7 @@
     public void close() {
         synchronized(this) {
             mClosed = true;
-            for (DictAndProximity dict : this) {
+            for (DictAndKeyboard dict : this) {
                 dict.mDictionary.close();
             }
             clear();
@@ -123,7 +123,7 @@
     }
 
     @Override
-    public boolean offer(final DictAndProximity dict) {
+    public boolean offer(final DictAndKeyboard dict) {
         if (mClosed) {
             dict.mDictionary.close();
             return super.offer(dummyDict);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
deleted file mode 100644
index 0c480ea..0000000
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.spellcheck;
-
-import android.util.SparseIntArray;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.Constants;
-
-public final class SpellCheckerProximityInfo extends ProximityInfo {
-    public SpellCheckerProximityInfo(final int script) {
-        super(getProximityForScript(script), PROXIMITY_GRID_WIDTH, PROXIMITY_GRID_HEIGHT);
-    }
-
-    private static final int NUL = Constants.NOT_A_CODE;
-
-    // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
-    // native code - this value is passed at creation of the binary object and reused
-    // as the size of the passed array afterwards so they can't be different.
-    private static final int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
-
-    // The number of keys in a row of the grid used by the spell checker.
-    private static final int PROXIMITY_GRID_WIDTH = 11;
-    // The number of rows in the grid used by the spell checker.
-    private static final int PROXIMITY_GRID_HEIGHT = 3;
-
-    private static final int NOT_AN_INDEX = -1;
-    public static final int NOT_A_COORDINATE_PAIR = -1;
-
-    // Helper methods
-    static void buildProximityIndices(final int[] proximity, final int rowSize,
-            final SparseIntArray indices) {
-        for (int i = 0; i < proximity.length; i += rowSize) {
-            if (NUL != proximity[i]) indices.put(proximity[i], i / rowSize);
-        }
-    }
-
-    private static final class Latin {
-        // The proximity here is the union of
-        // - the proximity for a QWERTY keyboard.
-        // - the proximity for an AZERTY keyboard.
-        // - the proximity for a QWERTZ keyboard.
-        // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
-        //
-        // The reasoning behind this construction is, almost any alphabetic text we may want
-        // to spell check has been entered with one of the keyboards above. Also, specifically
-        // to English, many spelling errors consist of the last vowel of the word being wrong
-        // because in English vowels tend to merge with each other in pronunciation.
-        /*
-        The Qwerty layout this represents looks like the following:
-            q w e r t y u i o p
-             a s d f g h j k l
-               z x c v b n m
-        */
-        static final int[] PROXIMITY = {
-            // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
-            // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
-            // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
-            'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
-            'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
-            'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            // Proximity for row 2. See comment above about size.
-            'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
-            's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            // Proximity for row 3. See comment above about size.
-            'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
-            'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        };
-
-        // This is a mapping array from the code point to the index in the PROXIMITY array.
-        // When we check the spelling of a word, we need to pass (x,y) coordinates to the native
-        // code for each letter of the word. These are most easily computed from the index in the
-        // PROXIMITY array. Since we'll need to do that very often, the index lookup from the code
-        // point needs to be as fast as possible, and a map is probably the best way to do this.
-        // To avoid unnecessary boxing conversion to Integer, here we use SparseIntArray.
-        static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
-
-        static {
-            buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
-        }
-    }
-
-    private static final class Cyrillic {
-        // TODO: The following table is solely based on the keyboard layout. Consult with Russian
-        // speakers on commonly misspelled words/letters.
-        /*
-        The Russian layout this represents looks like the following:
-            й ц у к е н г ш щ з х
-            ф ы в а п р о л д ж э
-              я ч с м и т ь б ю
-
-        This gives us the following table:
-            'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'к', 'у', 'в', 'а', 'п', 'е', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'е', 'к', 'а', 'п', 'р', 'н', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'н', 'е', 'п', 'р', 'о', 'г', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'г', 'н', 'р', 'о', 'л', 'ш', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ш', 'г', 'о', 'л', 'д', 'щ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'щ', 'ш', 'л', 'д', 'ж', 'з', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'а', 'у', 'к', 'е', 'в', 'п', 'ч', 'с', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'п', 'к', 'е', 'н', 'а', 'р', 'с', 'м', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'р', 'е', 'н', 'г', 'п', 'о', 'м', 'и', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'о', 'н', 'г', 'ш', 'р', 'л', 'и', 'т', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'л', 'г', 'ш', 'щ', 'о', 'д', 'т', 'ь', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'д', 'ш', 'щ', 'з', 'л', 'ж', 'ь', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'м', 'а', 'п', 'р', 'с', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'и', 'п', 'р', 'о', 'м', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'т', 'р', 'о', 'л', 'и', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-        Using the following characters:
-        */
-        private static final int CY_SHORT_I = '\u0439'; // й
-        private static final int CY_TSE = '\u0446'; // ц
-        private static final int CY_U = '\u0443'; // у
-        private static final int CY_KA = '\u043A'; // к
-        private static final int CY_IE = '\u0435'; // е
-        private static final int CY_EN = '\u043D'; // н
-        private static final int CY_GHE = '\u0433'; // г
-        private static final int CY_SHA = '\u0448'; // ш
-        private static final int CY_SHCHA = '\u0449'; // щ
-        private static final int CY_ZE = '\u0437'; // з
-        private static final int CY_HA = '\u0445'; // х
-        private static final int CY_EF = '\u0444'; // ф
-        private static final int CY_YERU = '\u044B'; // ы
-        private static final int CY_VE = '\u0432'; // в
-        private static final int CY_A = '\u0430'; // а
-        private static final int CY_PE = '\u043F'; // п
-        private static final int CY_ER = '\u0440'; // р
-        private static final int CY_O = '\u043E'; // о
-        private static final int CY_EL = '\u043B'; // л
-        private static final int CY_DE = '\u0434'; // д
-        private static final int CY_ZHE = '\u0436'; // ж
-        private static final int CY_E = '\u044D'; // э
-        private static final int CY_YA = '\u044F'; // я
-        private static final int CY_CHE = '\u0447'; // ч
-        private static final int CY_ES = '\u0441'; // с
-        private static final int CY_EM = '\u043C'; // м
-        private static final int CY_I = '\u0438'; // и
-        private static final int CY_TE = '\u0442'; // т
-        private static final int CY_SOFT_SIGN = '\u044C'; // ь
-        private static final int CY_BE = '\u0431'; // б
-        private static final int CY_YU = '\u044E'; // ю
-        static final int[] PROXIMITY = {
-            // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
-            // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
-            // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
-            CY_SHORT_I, CY_TSE, CY_EF, CY_YERU, NUL, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_TSE, CY_SHORT_I, CY_EF, CY_YERU, CY_VE, CY_U, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_U, CY_TSE, CY_YERU, CY_VE, CY_A, CY_KA, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_KA, CY_U, CY_VE, CY_A, CY_PE, CY_IE, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_IE, CY_KA, CY_A, CY_PE, CY_ER, CY_EN, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_EN, CY_IE, CY_PE, CY_ER, CY_O, CY_GHE, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_GHE, CY_EN, CY_ER, CY_O, CY_EL, CY_SHA, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_SHA, CY_GHE, CY_O, CY_EL, CY_DE, CY_SHCHA, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_SHCHA, CY_SHA, CY_EL, CY_DE, CY_ZHE, CY_ZE, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_ZE, CY_SHCHA, CY_DE, CY_ZHE, CY_E, CY_HA, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_HA, CY_ZE, CY_ZHE, CY_E, NUL, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            // Proximity for row 2. See comment above about size.
-            CY_EF, CY_SHORT_I, CY_TSE, CY_YERU, CY_YA, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_YERU, CY_SHORT_I, CY_TSE, CY_U, CY_EF, CY_VE, CY_YA, CY_CHE,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_VE, CY_TSE, CY_U, CY_KA, CY_YERU, CY_A, CY_YA, CY_CHE,
-                    CY_ES, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_A, CY_U, CY_KA, CY_IE, CY_VE, CY_PE, CY_CHE, CY_ES,
-                    CY_EM, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_PE, CY_KA, CY_IE, CY_EN, CY_A, CY_ER, CY_ES, CY_EM,
-                    CY_I, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_ER, CY_IE, CY_EN, CY_GHE, CY_PE, CY_O, CY_EM, CY_I,
-                    CY_TE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_O, CY_EN, CY_GHE, CY_SHA, CY_ER, CY_EL, CY_I, CY_TE,
-                    CY_SOFT_SIGN, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_EL, CY_GHE, CY_SHA, CY_SHCHA, CY_O, CY_DE, CY_TE, CY_SOFT_SIGN,
-                    CY_BE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_DE, CY_SHA, CY_SHCHA, CY_ZE, CY_EL, CY_ZHE, CY_SOFT_SIGN, CY_BE,
-                    CY_YU, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_ZHE, CY_SHCHA, CY_ZE, CY_HA, CY_DE, CY_E, CY_BE, CY_YU,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_E, CY_ZE, CY_HA, CY_YU, NUL, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            // Proximity for row 3. See comment above about size.
-            CY_YA, CY_EF, CY_YERU, CY_VE, CY_CHE, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_CHE, CY_YERU, CY_VE, CY_A, CY_YA, CY_ES, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_ES, CY_VE, CY_A, CY_PE, CY_CHE, CY_EM, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_EM, CY_A, CY_PE, CY_ER, CY_ES, CY_I, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_I, CY_PE, CY_ER, CY_O, CY_EM, CY_TE, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_TE, CY_ER, CY_O, CY_EL, CY_I, CY_SOFT_SIGN, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_SOFT_SIGN, CY_O, CY_EL, CY_DE, CY_TE, CY_BE, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_BE, CY_EL, CY_DE, CY_ZHE, CY_SOFT_SIGN, CY_YU, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            CY_YU, CY_DE, CY_ZHE, CY_E, CY_BE, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        };
-
-        static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
-
-        static {
-            buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
-        }
-    }
-
-    private static final class Greek {
-        // TODO: The following table is solely based on the keyboard layout. Consult with Greek
-        // speakers on commonly misspelled words/letters.
-        /*
-        The Greek layout this represents looks like the following:
-            ; ς ε ρ τ υ θ ι ο π
-             α σ δ φ γ η ξ κ λ
-               ζ χ ψ ω β ν μ
-
-        This gives us the following table:
-            'ς', 'ε', 'α', 'σ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ε', 'ς', 'ρ', 'σ', 'δ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ρ', 'ε', 'τ', 'δ', 'φ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'τ', 'ρ', 'υ', 'φ', 'γ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'υ', 'τ', 'θ', 'γ', 'η', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'θ', 'υ', 'ι', 'η', 'ξ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ι', 'θ', 'ο', 'ξ', 'κ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ο', 'ι', 'π', 'κ', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'π', 'ο', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            'α', 'ς', 'σ', 'ζ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'σ', 'ς', 'ε', 'α', 'δ', 'ζ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'δ', 'ε', 'ρ', 'σ', 'φ', 'ζ', 'χ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'φ', 'ρ', 'τ', 'δ', 'γ', 'χ', 'ψ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'γ', 'τ', 'υ', 'φ', 'η', 'ψ', 'ω', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'η', 'υ', 'θ', 'γ', 'ξ', 'ω', 'β', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ξ', 'θ', 'ι', 'η', 'κ', 'β', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'κ', 'ι', 'ο', 'ξ', 'λ', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'λ', 'ο', 'π', 'κ', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            'ζ', 'α', 'σ', 'δ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'χ', 'σ', 'δ', 'φ', 'ζ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ψ', 'δ', 'φ', 'γ', 'χ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ω', 'φ', 'γ', 'η', 'ψ', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'β', 'γ', 'η', 'ξ', 'ω', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'ν', 'η', 'ξ', 'κ', 'β', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            'μ', 'ξ', 'κ', 'λ', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-        Using the following characters:
-        */
-        private static final int GR_FINAL_SIGMA = '\u03C2'; // ς
-        private static final int GR_EPSILON = '\u03B5'; // ε
-        private static final int GR_RHO = '\u03C1'; // ρ
-        private static final int GR_TAU = '\u03C4'; // τ
-        private static final int GR_UPSILON = '\u03C5'; // υ
-        private static final int GR_THETA = '\u03B8'; // θ
-        private static final int GR_IOTA = '\u03B9'; // ι
-        private static final int GR_OMICRON = '\u03BF'; // ο
-        private static final int GR_PI = '\u03C0'; // π
-        private static final int GR_ALPHA = '\u03B1'; // α
-        private static final int GR_SIGMA = '\u03C3'; // σ
-        private static final int GR_DELTA = '\u03B4'; // δ
-        private static final int GR_PHI = '\u03C6'; // φ
-        private static final int GR_GAMMA = '\u03B3'; // γ
-        private static final int GR_ETA = '\u03B7'; // η
-        private static final int GR_XI = '\u03BE'; // ξ
-        private static final int GR_KAPPA = '\u03BA'; // κ
-        private static final int GR_LAMDA = '\u03BB'; // λ
-        private static final int GR_ZETA = '\u03B6'; // ζ
-        private static final int GR_CHI = '\u03C7'; // χ
-        private static final int GR_PSI = '\u03C8'; // ψ
-        private static final int GR_OMEGA = '\u03C9'; // ω
-        private static final int GR_BETA = '\u03B2'; // β
-        private static final int GR_NU = '\u03BD'; // ν
-        private static final int GR_MU = '\u03BC'; // μ
-        static final int[] PROXIMITY = {
-            // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
-            // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
-            // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
-            GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_SIGMA, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_EPSILON, GR_FINAL_SIGMA, GR_RHO, GR_SIGMA, GR_DELTA, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_RHO, GR_EPSILON, GR_TAU, GR_DELTA, GR_PHI, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_TAU, GR_RHO, GR_UPSILON, GR_PHI, GR_GAMMA, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_UPSILON, GR_TAU, GR_THETA, GR_GAMMA, GR_ETA, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_THETA, GR_UPSILON, GR_IOTA, GR_ETA, GR_XI, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_IOTA, GR_THETA, GR_OMICRON, GR_XI, GR_KAPPA, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_OMICRON, GR_IOTA, GR_PI, GR_KAPPA, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_PI, GR_OMICRON, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            GR_ALPHA, GR_FINAL_SIGMA, GR_SIGMA, GR_ZETA, NUL, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_SIGMA, GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_DELTA, GR_ZETA, GR_CHI, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_DELTA, GR_EPSILON, GR_RHO, GR_SIGMA, GR_PHI, GR_ZETA, GR_CHI, GR_PSI,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_PHI, GR_RHO, GR_TAU, GR_DELTA, GR_GAMMA, GR_CHI, GR_PSI, GR_OMEGA,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_GAMMA, GR_TAU, GR_UPSILON, GR_PHI, GR_ETA, GR_PSI, GR_OMEGA, GR_BETA,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_ETA, GR_UPSILON, GR_THETA, GR_GAMMA, GR_XI, GR_OMEGA, GR_BETA, GR_NU,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_XI, GR_THETA, GR_IOTA, GR_ETA, GR_KAPPA, GR_BETA, GR_NU, GR_MU,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_KAPPA, GR_IOTA, GR_OMICRON, GR_XI, GR_LAMDA, GR_NU, GR_MU, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_LAMDA, GR_OMICRON, GR_PI, GR_KAPPA, GR_MU, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-
-            GR_ZETA, GR_ALPHA, GR_SIGMA, GR_DELTA, GR_CHI, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_CHI, GR_SIGMA, GR_DELTA, GR_PHI, GR_ZETA, GR_PSI, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_PSI, GR_DELTA, GR_PHI, GR_GAMMA, GR_CHI, GR_OMEGA, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_OMEGA, GR_PHI, GR_GAMMA, GR_ETA, GR_PSI, GR_BETA, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_BETA, GR_GAMMA, GR_ETA, GR_XI, GR_OMEGA, GR_NU, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_NU, GR_ETA, GR_XI, GR_KAPPA, GR_BETA, GR_MU, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            GR_MU, GR_XI, GR_KAPPA, GR_LAMDA, GR_NU, NUL, NUL, NUL,
-                    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
-        };
-
-        static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
-
-        static {
-            buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
-        }
-    }
-
-    private static int[] getProximityForScript(final int script) {
-        switch (script) {
-        case AndroidSpellCheckerService.SCRIPT_LATIN:
-            return Latin.PROXIMITY;
-        case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
-            return Cyrillic.PROXIMITY;
-        case AndroidSpellCheckerService.SCRIPT_GREEK:
-            return Greek.PROXIMITY;
-        default:
-            throw new RuntimeException("Wrong script supplied: " + script);
-        }
-    }
-
-    private static int getIndexOfCodeForScript(final int codePoint, final int script) {
-        switch (script) {
-        case AndroidSpellCheckerService.SCRIPT_LATIN:
-            return Latin.INDICES.get(codePoint, NOT_AN_INDEX);
-        case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
-            return Cyrillic.INDICES.get(codePoint, NOT_AN_INDEX);
-        case AndroidSpellCheckerService.SCRIPT_GREEK:
-            return Greek.INDICES.get(codePoint, NOT_AN_INDEX);
-        default:
-            throw new RuntimeException("Wrong script supplied: " + script);
-        }
-    }
-
-    // Returns (Y << 16) + X to avoid creating a temporary object. This is okay because
-    // X and Y are limited to PROXIMITY_GRID_WIDTH resp. PROXIMITY_GRID_HEIGHT which is very
-    // inferior to 1 << 16
-    // As an exception, this returns NOT_A_COORDINATE_PAIR if the key is not on the grid
-    public static int getXYForCodePointAndScript(final int codePoint, final int script) {
-        final int index = getIndexOfCodeForScript(codePoint, script);
-        if (NOT_AN_INDEX == index) return NOT_A_COORDINATE_PAIR;
-        final int y = index / PROXIMITY_GRID_WIDTH;
-        final int x = index % PROXIMITY_GRID_WIDTH;
-        if (y > PROXIMITY_GRID_HEIGHT) {
-            // Safety check, should be entirely useless
-            throw new RuntimeException("Wrong y coordinate in spell checker proximity");
-        }
-        return (y << 16) + x;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
index 5f4c446..58c8f26 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -74,14 +74,14 @@
 
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0,
+                R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_menu_add);
+        actionItemAdd.setShowAsAction(
+                MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
         final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0,
                 R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete);
         actionItemDelete.setShowAsAction(
                 MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-        final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0,
-                R.string.user_dict_settings_delete).setIcon(R.drawable.ic_menu_add);
-        actionItemAdd.setShowAsAction(
-                MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index 36bc5ba..50dda96 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -108,6 +108,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/research/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
index 4249af5..8b64de8 100644
--- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
@@ -65,6 +65,7 @@
         final int numWordsIncoming = newLogUnit.getNumWords();
         if (mNumActualWords >= mWordCapacity) {
             // Give subclass a chance to handle the buffer full condition by shifting out logUnits.
+            // TODO: Tell onBufferFull() how much space it needs to make to avoid forced eviction.
             onBufferFull();
             // If still full, evict.
             if (mNumActualWords >= mWordCapacity) {
@@ -119,21 +120,19 @@
     /**
      * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed.
      *
-     * If there are less than {@code numWords} word-containing {@link LogUnit}s, shifts out
-     * all {@code LogUnit}s in the buffer.
+     * If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s.
      *
-     * @param numWords the minimum number of word-containing {@link LogUnit}s to shift out
-     * @return the number of actual {@code LogUnit}s shifted out
+     * @param numWords the minimum number of words in {@link LogUnit}s to shift out
+     * @return the number of actual words LogUnit}s shifted out
      */
     protected int shiftOutWords(final int numWords) {
-        int numWordContainingLogUnitsShiftedOut = 0;
-        for (LogUnit logUnit = shiftOut(); logUnit != null
-                && numWordContainingLogUnitsShiftedOut < numWords; logUnit = shiftOut()) {
-            if (logUnit.hasOneOrMoreWords()) {
-                numWordContainingLogUnitsShiftedOut += logUnit.getNumWords();
-            }
-        }
-        return numWordContainingLogUnitsShiftedOut;
+        int numWordsShiftedOut = 0;
+        do {
+            final LogUnit logUnit = shiftOut();
+            if (logUnit == null) break;
+            numWordsShiftedOut += logUnit.getNumWords();
+        } while (numWordsShiftedOut < numWords);
+        return numWordsShiftedOut;
     }
 
     public void shiftOutAll() {
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 42ef5d3..9bdedbf 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -190,22 +190,30 @@
     }
 
     protected final void publishLogUnitsAtFrontOfBuffer() {
+        // TODO: Refactor this method to require fewer passes through the LogUnits.  Should really
+        // require only one pass.
         ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE);
         if (isSafeNGram(logUnits, N_GRAM_SIZE)) {
             // Good n-gram at the front of the buffer.  Publish it, disclosing details.
             publish(logUnits, true /* canIncludePrivateData */);
             shiftOutWords(N_GRAM_SIZE);
             mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams;
-        } else {
-            // No good n-gram at front, and buffer is full.  Shift out up through the first logUnit
-            // with associated words (or if there is none, all the existing logUnits).
-            logUnits.clear();
-            for (LogUnit logUnit = shiftOut(); logUnit != null && !logUnit.hasOneOrMoreWords();
-                    logUnit = shiftOut()) {
-                logUnits.add(logUnit);
-            }
-            publish(logUnits, false /* canIncludePrivateData */);
+            return;
         }
+        // No good n-gram at front, and buffer is full.  Shift out up through the first logUnit
+        // with associated words (or if there is none, all the existing logUnits).
+        logUnits.clear();
+        LogUnit logUnit = shiftOut();
+        while (logUnit != null) {
+            logUnits.add(logUnit);
+            final int numWords = logUnit.getNumWords();
+            if (numWords > 0) {
+                mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWords);
+                break;
+            }
+            logUnit = shiftOut();
+        }
+        publish(logUnits, false /* canIncludePrivateData */);
     }
 
     /**
@@ -222,12 +230,11 @@
 
     @Override
     protected int shiftOutWords(final int numWords) {
-        final int numWordContainingLogUnitsShiftedOut = super.shiftOutWords(numWords);
-        mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample
-                - numWordContainingLogUnitsShiftedOut);
+        final int numWordsShiftedOut = super.shiftOutWords(numWords);
+        mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWordsShiftedOut);
         if (DEBUG) {
             Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample);
         }
-        return numWordContainingLogUnitsShiftedOut;
+        return numWordsShiftedOut;
     }
 }
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 11fa3da..1dd68ea 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -109,7 +109,8 @@
     }
     Dictionary *dictionary = 0;
     if (BinaryFormat::UNKNOWN_FORMAT
-            == BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf))) {
+            == BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf),
+                    static_cast<int>(dictSize))) {
         AKLOGE("DICT: dictionary format is unknown, bad magic number");
 #ifdef USE_MMAP_FOR_DICTIONARY
         releaseDictBuf(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 06f50dc..9824153 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -64,13 +64,14 @@
     static const int UNKNOWN_FORMAT = -1;
     static const int SHORTCUT_LIST_SIZE_SIZE = 2;
 
-    static int detectFormat(const uint8_t *const dict);
-    static int getHeaderSize(const uint8_t *const dict);
-    static int getFlags(const uint8_t *const dict);
+    static int detectFormat(const uint8_t *const dict, const int dictSize);
+    static int getHeaderSize(const uint8_t *const dict, const int dictSize);
+    static int getFlags(const uint8_t *const dict, const int dictSize);
     static bool hasBlacklistedOrNotAWordFlag(const int flags);
-    static void readHeaderValue(const uint8_t *const dict, const char *const key, int *outValue,
-            const int outValueSize);
-    static int readHeaderValueInt(const uint8_t *const dict, const char *const key);
+    static void readHeaderValue(const uint8_t *const dict, const int dictSize,
+            const char *const key, int *outValue, const int outValueSize);
+    static int readHeaderValueInt(const uint8_t *const dict, const int dictSize,
+            const char *const key);
     static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos);
     static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos);
     static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos);
@@ -96,7 +97,7 @@
             const uint8_t *bigramFilter, const int unigramProbability);
     static int getBigramProbabilityFromHashMap(const int position,
             const hash_map_compat<int, int> *bigramMap, const int unigramProbability);
-    static float getMultiWordCostMultiplier(const uint8_t *const dict);
+    static float getMultiWordCostMultiplier(const uint8_t *const dict, const int dictSize);
     static void fillBigramProbabilityToHashMap(const uint8_t *const root, int position,
             hash_map_compat<int, int> *bigramMap);
     static int getBigramProbability(const uint8_t *const root, int position,
@@ -122,6 +123,8 @@
     static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
     static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
 
+    // Any file smaller than this is not a dictionary.
+    static const int DICTIONARY_MINIMUM_SIZE = 4;
     // Originally, format version 1 had a 16-bit magic number, then the version number `01'
     // then options that must be 0. Hence the first 32-bits of the format are always as follow
     // and it's okay to consider them a magic number as a whole.
@@ -131,6 +134,8 @@
     // number, so we had to change it so that version 2 files would be rejected by older
     // implementations. On this occasion, we made the magic number 32 bits long.
     static const int FORMAT_VERSION_2_MAGIC_NUMBER = -1681835266; // 0x9BC13AFE
+    // Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12
+    static const int FORMAT_VERSION_2_MINIMUM_SIZE = 12;
 
     static const int CHARACTER_ARRAY_TERMINATOR_SIZE = 1;
     static const int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
@@ -141,8 +146,11 @@
     static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
 };
 
-AK_FORCE_INLINE int BinaryFormat::detectFormat(const uint8_t *const dict) {
+AK_FORCE_INLINE int BinaryFormat::detectFormat(const uint8_t *const dict, const int dictSize) {
     // The magic number is stored big-endian.
+    // If the dictionary is less than 4 bytes, we can't even read the magic number, so we don't
+    // understand this format.
+    if (dictSize < DICTIONARY_MINIMUM_SIZE) return UNKNOWN_FORMAT;
     const int magicNumber = (dict[0] << 24) + (dict[1] << 16) + (dict[2] << 8) + dict[3];
     switch (magicNumber) {
     case FORMAT_VERSION_1_MAGIC_NUMBER:
@@ -152,6 +160,10 @@
         // Options (2 bytes) must be 0x00 0x00
         return 1;
     case FORMAT_VERSION_2_MAGIC_NUMBER:
+        // Version 2 dictionaries are at least 12 bytes long (see below details for the header).
+        // If this dictionary has the version 2 magic number but is less than 12 bytes long, then
+        // it's an unknown format and we need to avoid confidently reading the next bytes.
+        if (dictSize < FORMAT_VERSION_2_MINIMUM_SIZE) return UNKNOWN_FORMAT;
         // Format 2 header is as follows:
         // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
         // Version number (2 bytes) 0x00 0x02
@@ -163,8 +175,8 @@
     }
 }
 
-inline int BinaryFormat::getFlags(const uint8_t *const dict) {
-    switch (detectFormat(dict)) {
+inline int BinaryFormat::getFlags(const uint8_t *const dict, const int dictSize) {
+    switch (detectFormat(dict, dictSize)) {
     case 1:
         return NO_FLAGS; // TODO: NO_FLAGS is unused anywhere else?
     default:
@@ -176,8 +188,8 @@
     return (flags & (FLAG_IS_BLACKLISTED | FLAG_IS_NOT_A_WORD)) != 0;
 }
 
-inline int BinaryFormat::getHeaderSize(const uint8_t *const dict) {
-    switch (detectFormat(dict)) {
+inline int BinaryFormat::getHeaderSize(const uint8_t *const dict, const int dictSize) {
+    switch (detectFormat(dict, dictSize)) {
     case 1:
         return FORMAT_VERSION_1_HEADER_SIZE;
     case 2:
@@ -188,12 +200,12 @@
     }
 }
 
-inline void BinaryFormat::readHeaderValue(const uint8_t *const dict, const char *const key,
-        int *outValue, const int outValueSize) {
+inline void BinaryFormat::readHeaderValue(const uint8_t *const dict, const int dictSize,
+        const char *const key, int *outValue, const int outValueSize) {
     int outValueIndex = 0;
     // Only format 2 and above have header attributes as {key,value} string pairs. For prior
     // formats, we just return an empty string, as if the key wasn't found.
-    if (2 <= detectFormat(dict)) {
+    if (2 <= detectFormat(dict, dictSize)) {
         const int headerOptionsOffset = 4 /* magic number */
                 + 2 /* dictionary version */ + 2 /* flags */;
         const int headerSize =
@@ -236,11 +248,12 @@
     if (outValueIndex >= 0) outValue[outValueIndex] = 0;
 }
 
-inline int BinaryFormat::readHeaderValueInt(const uint8_t *const dict, const char *const key) {
+inline int BinaryFormat::readHeaderValueInt(const uint8_t *const dict, const int dictSize,
+        const char *const key) {
     const int bufferSize = LARGEST_INT_DIGIT_COUNT;
     int intBuffer[bufferSize];
     char charBuffer[bufferSize];
-    BinaryFormat::readHeaderValue(dict, key, intBuffer, bufferSize);
+    BinaryFormat::readHeaderValue(dict, dictSize, key, intBuffer, bufferSize);
     for (int i = 0; i < bufferSize; ++i) {
         charBuffer[i] = intBuffer[i];
     }
@@ -256,8 +269,10 @@
     return ((msb & 0x7F) << 8) | dict[(*pos)++];
 }
 
-inline float BinaryFormat::getMultiWordCostMultiplier(const uint8_t *const dict) {
-    const int headerValue = readHeaderValueInt(dict, "MULTIPLE_WORDS_DEMOTION_RATE");
+inline float BinaryFormat::getMultiWordCostMultiplier(const uint8_t *const dict,
+        const int dictSize) {
+    const int headerValue = readHeaderValueInt(dict, dictSize,
+            "MULTIPLE_WORDS_DEMOTION_RATE");
     if (headerValue == S_INT_MIN) {
         return 1.0f;
     }
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index c998c06..dadb2ba 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -34,9 +34,11 @@
 
 Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust)
         : mDict(static_cast<unsigned char *>(dict)),
-          mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
+          mOffsetDict((static_cast<unsigned char *>(dict))
+                  + BinaryFormat::getHeaderSize(mDict, dictSize)),
           mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
-          mUnigramDictionary(new UnigramDictionary(mOffsetDict, BinaryFormat::getFlags(mDict))),
+          mUnigramDictionary(new UnigramDictionary(mOffsetDict,
+                  BinaryFormat::getFlags(mDict, dictSize))),
           mBigramDictionary(new BigramDictionary(mOffsetDict)),
           mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
           mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 5116585..6408f01 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -64,7 +64,8 @@
 void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
         int prevWordLength) {
     mDictionary = dictionary;
-    mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier(mDictionary->getDict());
+    mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier(mDictionary->getDict(),
+            mDictionary->getDictSize());
     if (!prevWord) {
         mPrevWordPos = NOT_VALID_WORD;
         return;
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 1e3cc8a..b6a17a3 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -237,4 +237,63 @@
         // code for now True is acceptable.
         assertTrue(StringUtils.lastPartLooksLikeURL(".abc/def"));
     }
+
+    public void testFindValueOfKey() {
+        final String nullKey = null;
+        final String emptyKey = "";
+
+        final String[] nullArray = null;
+        assertNull(StringUtils.findValueOfKey("anyKey", nullArray));
+        assertNull(StringUtils.findValueOfKey(emptyKey, nullArray));
+        assertNull(StringUtils.findValueOfKey(nullKey, nullArray));
+
+        final String[] emptyArray = {};
+        assertNull(StringUtils.findValueOfKey("anyKey", emptyArray));
+        assertNull(StringUtils.findValueOfKey(emptyKey, emptyArray));
+        assertNull(StringUtils.findValueOfKey(nullKey, emptyArray));
+
+        final String[] array = {
+            "DEFAULT,defaultValue",
+            "HARDWARE=grouper,0.3",
+            "HARDWARE=mako,0.4",
+            "HARDWARE=manta,0.2"
+        };
+        assertEquals(StringUtils.findValueOfKey("HARDWARE=grouper", array), "0.3");
+        assertEquals(StringUtils.findValueOfKey("HARDWARE=mako", array), "0.4");
+        assertEquals(StringUtils.findValueOfKey("HARDWARE=manta", array), "0.2");
+        assertEquals(StringUtils.findValueOfKey("DEFAULT", array), "defaultValue");
+
+        assertNull(StringUtils.findValueOfKey("hardware=grouper", array));
+        assertNull(StringUtils.findValueOfKey("HARDWARE=MAKO", array));
+        assertNull(StringUtils.findValueOfKey("HARDWARE=mantaray", array));
+        assertNull(StringUtils.findValueOfKey(emptyKey, array));
+        assertNull(StringUtils.findValueOfKey(nullKey, array));
+
+        final String[] containsNullKey = {
+            "DEFAULT,defaultValue",
+            ",emptyValue"
+        };
+        assertEquals(StringUtils.findValueOfKey(emptyKey, containsNullKey), "emptyValue");
+
+        final String[] containsMultipleSameKeys = {
+            "key1,value1",
+            "key2,value2",
+            "key3,value3",
+            "key2,value4"
+        };
+        assertEquals(StringUtils.findValueOfKey("key2", containsMultipleSameKeys), "value2");
+
+        final String[] containNoCommaElement = {
+            "key1,value1",
+            "key2-and-value2",
+            "key3,value3"
+        };
+        assertEquals(StringUtils.findValueOfKey("key1", containNoCommaElement), "value1");
+        try {
+            final String valueOfKey3 = StringUtils.findValueOfKey("key3", containNoCommaElement);
+            fail("finding valueOfKey3=" + valueOfKey3 + " must fail");
+        } catch (final RuntimeException e) {
+            assertEquals(e.getMessage(), "Element has no comma: key2-and-value2");
+        }
+    }
 }