Merge "Remove ref to LatinImeLogger preference."
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index e39ca18..fb973f3 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -57,14 +57,22 @@
         <activity android:name=".setup.SetupActivity"
                 android:label="@string/english_ime_name"
                 android:icon="@mipmap/ic_launcher_keyboard"
-                android:launchMode="singleTop"
-                android:clearTaskOnLaunch="true">
+                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" />
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/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/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/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/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 312fd21..ccb8802 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -152,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();
@@ -189,11 +189,17 @@
                 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/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/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/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index acb0766..8a2de88 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -17,265 +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.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);
-                mp.setLooping(true);
-            }
-        });
-        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);
     }
 
     /**
@@ -312,170 +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();
-    }
-
-    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 && 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.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/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/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index cd2f994..c8242c8 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -149,18 +149,18 @@
     private static final ResearchLogger sInstance = new ResearchLogger();
     private static String sAccountType = null;
     private static String sAllowedAccountDomain = null;
-    /* package */ ResearchLog mMainResearchLog;
+    private ResearchLog mMainResearchLog; // always non-null after init() is called
     // mFeedbackLog records all events for the session, private or not (excepting
     // passwords).  It is written to permanent storage only if the user explicitly commands
     // the system to do so.
     // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are
     // complete.
-    /* package */ MainLogBuffer mMainLogBuffer;
+    /* package for test */ MainLogBuffer mMainLogBuffer; // always non-null after init() is called
     // TODO: Remove the feedback log.  The feedback log continuously captured user data in case the
     // user wanted to submit it.  We now use the mUserRecordingLogBuffer to allow the user to
     // explicitly reproduce a problem.
-    /* package */ ResearchLog mFeedbackLog;
-    /* package */ LogBuffer mFeedbackLogBuffer;
+    private ResearchLog mFeedbackLog;
+    private LogBuffer mFeedbackLogBuffer;
     /* package */ ResearchLog mUserRecordingLog;
     /* package */ LogBuffer mUserRecordingLogBuffer;
     private File mUserRecordingFile = null;
@@ -240,6 +240,9 @@
         mResearchLogDirectory = new ResearchLogDirectory(mLatinIME);
         cleanLogDirectoryIfNeeded(mResearchLogDirectory, System.currentTimeMillis());
 
+        // Initialize log buffers
+        resetLogBuffers();
+
         // Initialize external services
         mUploadIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
@@ -251,6 +254,39 @@
         mReplayer.setKeyboardSwitcher(keyboardSwitcher);
     }
 
+    private void resetLogBuffers() {
+        mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
+                System.currentTimeMillis(), System.nanoTime()), mLatinIME);
+        final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
+        mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
+                mSuggest) {
+            @Override
+            protected void publish(final ArrayList<LogUnit> logUnits,
+                    boolean canIncludePrivateData) {
+                canIncludePrivateData |= IS_LOGGING_EVERYTHING;
+                for (final LogUnit logUnit : logUnits) {
+                    if (DEBUG) {
+                        final String wordsString = logUnit.getWordsAsString();
+                        Log.d(TAG, "onPublish: '" + wordsString
+                                + "', hc: " + logUnit.containsCorrection()
+                                + ", cipd: " + canIncludePrivateData);
+                    }
+                    for (final String word : logUnit.getWordsAsStringArray()) {
+                        final Dictionary dictionary = getDictionary();
+                        mStatistics.recordWordEntered(
+                                dictionary != null && dictionary.isValidWord(word),
+                                logUnit.containsCorrection());
+                    }
+                }
+                publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData);
+            }
+        };
+
+        mFeedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
+                System.currentTimeMillis(), System.nanoTime()), mLatinIME);
+        mFeedbackLogBuffer = new FixedLogBuffer(FEEDBACK_WORD_BUFFER_SIZE);
+    }
+
     private void cleanLogDirectoryIfNeeded(final ResearchLogDirectory researchLogDirectory,
             final long now) {
         final long lastCleanupTime = ResearchSettings.readResearchLastDirCleanupTime(mPrefs);
@@ -379,49 +415,6 @@
         requestIndicatorRedraw();
         mStatistics.reset();
         checkForEmptyEditor();
-        if (mFeedbackLogBuffer == null) {
-            resetFeedbackLogging();
-        }
-        if (!isAllowedToLog()) {
-            // Log.w(TAG, "not in usability mode; not logging");
-            return;
-        }
-        if (mMainLogBuffer == null) {
-            mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
-                    System.currentTimeMillis(), System.nanoTime()), mLatinIME);
-            final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
-            mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
-                    mSuggest) {
-                @Override
-                protected void publish(final ArrayList<LogUnit> logUnits,
-                        boolean canIncludePrivateData) {
-                    canIncludePrivateData |= IS_LOGGING_EVERYTHING;
-                    for (final LogUnit logUnit : logUnits) {
-                        if (DEBUG) {
-                            final String wordsString = logUnit.getWordsAsString();
-                            Log.d(TAG, "onPublish: '" + wordsString
-                                    + "', hc: " + logUnit.containsCorrection()
-                                    + ", cipd: " + canIncludePrivateData);
-                        }
-                        for (final String word : logUnit.getWordsAsStringArray()) {
-                            final Dictionary dictionary = getDictionary();
-                            mStatistics.recordWordEntered(
-                                    dictionary != null && dictionary.isValidWord(word),
-                                    logUnit.containsCorrection());
-                        }
-                    }
-                    if (mMainResearchLog != null) {
-                        publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData);
-                    }
-                }
-            };
-        }
-    }
-
-    private void resetFeedbackLogging() {
-        mFeedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
-                System.currentTimeMillis(), System.nanoTime()), mLatinIME);
-        mFeedbackLogBuffer = new FixedLogBuffer(FEEDBACK_WORD_BUFFER_SIZE);
     }
 
     /* package */ void stop() {
@@ -431,35 +424,27 @@
         // Commit mCurrentLogUnit before closing.
         commitCurrentLogUnit();
 
-        if (mMainLogBuffer != null) {
-            mMainLogBuffer.shiftAndPublishAll();
-            logStatistics();
-            commitCurrentLogUnit();
-            mMainLogBuffer.setIsStopping();
-            mMainLogBuffer.shiftAndPublishAll();
-            mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
-            mMainLogBuffer = null;
-        }
-        if (mFeedbackLogBuffer != null) {
-            mFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
-            mFeedbackLogBuffer = null;
-        }
+        mMainLogBuffer.shiftAndPublishAll();
+        logStatistics();
+        commitCurrentLogUnit();
+        mMainLogBuffer.setIsStopping();
+        mMainLogBuffer.shiftAndPublishAll();
+        mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
+        mFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
+
+        resetLogBuffers();
     }
 
     public void abort() {
         if (DEBUG) {
             Log.d(TAG, "abort called");
         }
-        if (mMainLogBuffer != null) {
-            mMainLogBuffer.clear();
-            mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
-            mMainLogBuffer = null;
-        }
-        if (mFeedbackLogBuffer != null) {
-            mFeedbackLogBuffer.clear();
-            mFeedbackLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
-            mFeedbackLogBuffer = null;
-        }
+        mMainLogBuffer.clear();
+        mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
+        mFeedbackLogBuffer.clear();
+        mFeedbackLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
+
+        resetLogBuffers();
     }
 
     private void restart() {
@@ -740,8 +725,8 @@
 
     public void initSuggest(final Suggest suggest) {
         mSuggest = suggest;
-        // MainLogBuffer has out-of-date Suggest object.  Need to close it down and create a new
-        // one.
+        // MainLogBuffer now has an out-of-date Suggest object.  Close down MainLogBuffer and create
+        // a new one.
         if (mMainLogBuffer != null) {
             stop();
             start();
@@ -852,9 +837,7 @@
                     ": " + mCurrentLogUnit.getWordsAsString() : ""));
         }
         if (!mCurrentLogUnit.isEmpty()) {
-            if (mMainLogBuffer != null) {
-                mMainLogBuffer.shiftIn(mCurrentLogUnit);
-            }
+            mMainLogBuffer.shiftIn(mCurrentLogUnit);
             if (mFeedbackLogBuffer != null) {
                 mFeedbackLogBuffer.shiftIn(mCurrentLogUnit);
             }
@@ -882,9 +865,6 @@
         //
         // Note that we don't use mLastLogUnit here, because it only goes one word back and is only
         // needed for reverts, which only happen one back.
-        if (mMainLogBuffer == null) {
-            return;
-        }
         final LogUnit oldLogUnit = mMainLogBuffer.peekLastLogUnit();
 
         // Check that expected word matches.
@@ -938,6 +918,7 @@
             final ResearchLog researchLog, final boolean canIncludePrivateData) {
         final LogUnit openingLogUnit = new LogUnit();
         if (logUnits.isEmpty()) return;
+        if (!isAllowedToLog()) return;
         // LogUnits not containing private data, such as contextual data for the log, do not require
         // logSegment boundary statements.
         if (canIncludePrivateData) {
@@ -1371,11 +1352,7 @@
     public static void latinIME_promotePhantomSpace() {
         final ResearchLogger researchLogger = getInstance();
         final LogUnit logUnit;
-        if (researchLogger.mMainLogBuffer == null) {
-            logUnit = researchLogger.mCurrentLogUnit;
-        } else {
-            logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
-        }
+        logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
         researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
     }
 
@@ -1392,11 +1369,7 @@
             final String charactersAfterSwap) {
         final ResearchLogger researchLogger = getInstance();
         final LogUnit logUnit;
-        if (researchLogger.mMainLogBuffer == null) {
-            logUnit = null;
-        } else {
-            logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
-        }
+        logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
         if (logUnit != null) {
             researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE,
                     originalCharacters, charactersAfterSwap);
@@ -1469,11 +1442,7 @@
         final ResearchLogger researchLogger = getInstance();
         // TODO: Verify that mCurrentLogUnit has been restored and contains the reverted word.
         final LogUnit logUnit;
-        if (researchLogger.mMainLogBuffer == null) {
-            logUnit = null;
-        } else {
-            logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
-        }
+        logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
         if (originallyTypedWord.length() > 0 && hasLetters(originallyTypedWord)) {
             if (logUnit != null) {
                 logUnit.setWords(originallyTypedWord);
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");
+        }
+    }
 }