Merge "[TestPrep4] Add some finals"
diff --git a/java/res/drawable-hdpi/ic_settings_language.png b/java/res/drawable-hdpi/ic_settings_language.png
new file mode 100644
index 0000000..f635b2e
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_settings_language.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_settings_language.png b/java/res/drawable-mdpi/ic_settings_language.png
new file mode 100644
index 0000000..f8aca67
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_settings_language.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_settings_language.png b/java/res/drawable-xhdpi/ic_settings_language.png
new file mode 100644
index 0000000..2c42db3
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_settings_language.png
Binary files differ
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index 40eff38..136e18c 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -43,8 +43,11 @@
             android:layout_width="@dimen/suggestions_strip_padding"
             android:layout_height="@dimen/suggestions_strip_height"
             style="?attr/suggestionsStripBackgroundStyle" />
+        <!-- To ensure that key preview popup is correctly placed when the current system locale is
+             one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
         <com.android.inputmethod.latin.suggestions.SuggestionStripView
             android:id="@+id/suggestion_strip_view"
+            android:layoutDirection="ltr"
             android:layout_weight="1.0"
             android:layout_width="0dp"
             android:layout_height="@dimen/suggestions_strip_height"
@@ -56,8 +59,11 @@
             style="?attr/suggestionsStripBackgroundStyle" />
     </LinearLayout>
 
+    <!-- To ensure that key preview popup is correctly placed when the current system locale is
+         one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
     <com.android.inputmethod.keyboard.MainKeyboardView
         android:id="@+id/keyboard_view"
+        android:layoutDirection="ltr"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
diff --git a/java/res/layout/setup_step.xml b/java/res/layout/setup_step.xml
new file mode 100644
index 0000000..26d7fe7
--- /dev/null
+++ b/java/res/layout/setup_step.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+    <TextView
+        android:id="@+id/setup_step_title"
+        style="@style/setupStepTitleStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp" />
+    <TextView
+        android:id="@+id/setup_step_instruction"
+        style="@style/setupStepInstructionStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="16dp"
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp" />
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="2dp" />
+    <TextView
+        android:id="@+id/setup_step_action_label"
+        style="@style/setupStepActionLabelStyle"
+        android:gravity="center_vertical"
+        android:drawablePadding="12dp"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:clickable="true"
+        android:focusable="true"
+        android:paddingLeft="12dp"
+        android:paddingStart="12dp"
+        android:paddingRight="24dp"
+        android:paddingEnd="24dp" />
+</LinearLayout>
diff --git a/java/res/layout/setup_wizard.xml b/java/res/layout/setup_wizard.xml
new file mode 100644
index 0000000..acbbe30
--- /dev/null
+++ b/java/res/layout/setup_wizard.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/setup_background"
+        android:paddingLeft="@dimen/setup_horizontal_padding"
+        android:paddingRight="@dimen/setup_horizontal_padding"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+        <TextView
+            android:id="@+id/setup_title"
+            style="@style/setupTitleStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <LinearLayout
+            android:id="@+id/setup_step_bullets"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/setup_title"
+            android:paddingTop="16dp"
+            android:orientation="horizontal">
+            <TextView
+                style="@style/setupStepBulletStyle"
+                android:text="@string/setup_step1_bullet" />
+            <TextView
+                style="@style/setupStepBulletStyle"
+                android:text="@string/setup_step2_bullet" />
+            <TextView
+                style="@style/setupStepBulletStyle"
+                android:text="@string/setup_step3_bullet" />
+        </LinearLayout>
+        <com.android.inputmethod.latin.setup.SetupStepIndicatorView
+            android:id="@+id/setup_step_indicator"
+            android:layout_width="match_parent"
+            android:layout_height="24dp"
+            android:layout_below="@id/setup_step_bullets" />
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/setup_step_indicator">
+            <include
+                android:id="@+id/setup_step1"
+                layout="@layout/setup_step" />
+            <include
+                android:id="@+id/setup_step2"
+                layout="@layout/setup_step" />
+            <include
+                android:id="@+id/setup_step3"
+                layout="@layout/setup_step" />
+        </FrameLayout>
+    </RelativeLayout>
+</ScrollView>
diff --git a/java/res/values-sw600dp-land/setup-dimens.xml b/java/res/values-sw600dp-land/setup-dimens.xml
new file mode 100644
index 0000000..9aea214
--- /dev/null
+++ b/java/res/values-sw600dp-land/setup-dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="setup_title_text_size">64sp</dimen>
+    <dimen name="setup_horizontal_padding">96dp</dimen>
+</resources>
diff --git a/java/res/values-sw768dp-land/setup-dimens.xml b/java/res/values-sw768dp-land/setup-dimens.xml
new file mode 100644
index 0000000..0d2af17
--- /dev/null
+++ b/java/res/values-sw768dp-land/setup-dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="setup_title_text_size">64sp</dimen>
+    <dimen name="setup_horizontal_padding">192dp</dimen>
+</resources>
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index c0ea321..8a8049f 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -53,4 +53,9 @@
     <color name="spacebar_text_color_ics">#FFC0C0C0</color>
     <color name="spacebar_text_shadow_color_ics">#80000000</color>
     <color name="typed_word_color_ics">@color/highlight_color_ics</color>
+    <!-- Color resources for setup wizard and tutorial -->
+    <color name="setup_background">#FFEBEBEB</color>
+    <color name="setup_text_dark">#FF707070</color>
+    <color name="setup_text_action">@android:color/holo_blue_light</color>
+    <color name="setup_step_background">@android:color/background_light</color>
 </resources>
diff --git a/java/res/values/setup-dimens.xml b/java/res/values/setup-dimens.xml
new file mode 100644
index 0000000..007906d
--- /dev/null
+++ b/java/res/values/setup-dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="setup_title_text_size">46sp</dimen>
+    <dimen name="setup_horizontal_padding">16dp</dimen>
+</resources>
diff --git a/java/res/values/setup-styles.xml b/java/res/values/setup-styles.xml
new file mode 100644
index 0000000..cfc689a
--- /dev/null
+++ b/java/res/values/setup-styles.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="setupTitleStyle">
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">@dimen/setup_title_text_size</item>
+    </style>
+    <style name="setupStepBulletStyle">
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">22sp</item>
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_weight">1.0</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:gravity">center_horizontal</item>
+    </style>
+    <style name="setupStepTitleStyle">
+        <item name="android:background">@color/setup_step_background</item>
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">22sp</item>
+    </style>
+    <style name="setupStepInstructionStyle">
+        <item name="android:background">@color/setup_step_background</item>
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+    <style name="setupStepActionLabelStyle">
+        <item name="android:background">@color/setup_step_background</item>
+        <item name="android:textColor">@color/setup_text_action</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index ee79b45..f7d34c8 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -408,4 +408,29 @@
 
     <!-- Title of the button to revert to the default value of the device in the settings dialog [CHAR LIMIT=15] -->
     <string name="button_default">Default</string>
+
+    <!-- Title of the setup wizard. [CHAR LIMT=40] -->
+    <string name="setup_title">"Installing <xliff:g id="application_name">%s</xliff:g>"</string>
+    <!-- Ordinal number of the 1st step in the setup wizard. [CHAR LIMIT=5] -->
+    <string name="setup_step1_bullet">1</string>
+    <!-- Title of the 1st step in the setup wizard. [CHAR LIMIT=64] -->
+    <string name="setup_step1_title">"Enable <xliff:g id="application_name">%s</xliff:g> in settings."</string>
+    <!-- Detailed instruction of the 1st step in the setup wizard. [CHAR LIMIT=80] -->
+    <string name="setup_step1_instruction">"For security, please check \"<xliff:g id="application_name">%s</xliff:g>\""</string>
+    <!-- Ordinal number of the 2nd step in the setup wizard. [CHAR LIMIT=5] -->
+    <string name="setup_step2_bullet">2</string>
+    <!-- Title of the 2nd step in the setup wizard. [CHAR LIMIT=64] -->
+    <string name="setup_step2_title">"Switch to <xliff:g id="application_name">%s</xliff:g>."</string>
+    <!-- Detailed instruction of the 2nd step in the setup wizard. [CHAR LIMIT=80] -->
+    <string name="setup_step2_instruction">"Now that you've enabled <xliff:g id="application_name">%s</xliff:g>, you can switch to it."</string>
+    <!-- Ordinal number of the 3rd step in the setup wizard. [CHAR LIMIT=5] -->
+    <string name="setup_step3_bullet">3</string>
+    <!-- Title of the 3rd step in the setup wizard. [CHAR LIMIT=64] -->
+    <string name="setup_step3_title">"Congratulations, you're all set!"</string>
+    <!-- Detailed instruction of the 3rd step in the setup wizard. [CHAR LIMIT=80] -->
+    <string name="setup_step3_instruction">Configure additional languages</string>
+    <!-- Title of the Language & input settings. This should be aligned with msgid="5292716747264442359" -->
+    <string name="language_settings">Language &amp; input</string>
+    <!-- Title of the Input method picker. This should be aligned with msgid="4653387336791222978" -->
+    <string name="select_input_method">Choose input method</string>
 </resources>
diff --git a/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java b/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java
new file mode 100644
index 0000000..d4f1ea8
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.graphics.drawable.Drawable;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+
+public final class TextViewCompatUtils {
+    // Note that TextView.setCompoundDrawablesRelative(Drawable,Drawable,Drawable,Drawable) has
+    // been introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
+    private static final Method METHOD_setCompoundDrawablesRelative = CompatUtils.getMethod(
+            TextView.class, "setCompoundDrawablesRelative",
+            Drawable.class, Drawable.class, Drawable.class, Drawable.class);
+
+    private TextViewCompatUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void setCompoundDrawablesRelative(final TextView textView, final Drawable start,
+            final Drawable top, final Drawable end, final Drawable bottom) {
+        if (METHOD_setCompoundDrawablesRelative == null) {
+            textView.setCompoundDrawables(start, top, end, bottom);
+            return;
+        }
+        CompatUtils.invoke(textView, null, METHOD_setCompoundDrawablesRelative,
+                start, top, end, bottom);
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
new file mode 100644
index 0000000..a8fab88
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.view.View;
+
+import java.lang.reflect.Method;
+
+public final class ViewCompatUtils {
+    // Note that View.LAYOUT_DIRECTION_LTR and View.LAYOUT_DIRECTION_RTL have been introduced in
+    // API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
+    public static final int LAYOUT_DIRECTION_LTR = (Integer)CompatUtils.getFieldValue(null, 0x0,
+            CompatUtils.getField(View.class, "LAYOUT_DIRECTION_LTR"));
+    public static final int LAYOUT_DIRECTION_RTL = (Integer)CompatUtils.getFieldValue(null, 0x1,
+            CompatUtils.getField(View.class, "LAYOUT_DIRECTION_RTL"));
+
+    // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int), and
+    // View.getLayoutDirection() have been introduced in API level 17
+    // (Build.VERSION_CODE.JELLY_BEAN_MR1).
+    private static final Method METHOD_getPaddingEnd = CompatUtils.getMethod(
+            View.class, "getPaddingEnd");
+    private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod(
+            View.class, "setPaddingRelative",
+            Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE);
+    private static final Method METHOD_getLayoutDirection = CompatUtils.getMethod(
+            View.class, "getLayoutDirection");
+
+    private ViewCompatUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static int getPaddingEnd(final View view) {
+        if (METHOD_getPaddingEnd == null) {
+            return view.getPaddingRight();
+        }
+        return (Integer)CompatUtils.invoke(view, 0, METHOD_getPaddingEnd);
+    }
+
+    public static void setPaddingRelative(final View view, final int start, final int top,
+            final int end, final int bottom) {
+        if (METHOD_setPaddingRelative == null) {
+            view.setPadding(start, top, end, bottom);
+            return;
+        }
+        CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom);
+    }
+
+    public static int getLayoutDirection(final View view) {
+        if (METHOD_getLayoutDirection == null) {
+            return LAYOUT_DIRECTION_LTR;
+        }
+        return (Integer)CompatUtils.invoke(view, 0, METHOD_getLayoutDirection);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index d9d664f..0d0ce57 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -74,6 +74,8 @@
 
     // The path fragment to append after the client ID for dictionary info requests.
     private static final String QUERY_PATH_DICT_INFO = "dict";
+    // The path fragment to append after the client ID for dictionary datafile requests.
+    private static final String QUERY_PATH_DATAFILE = "datafile";
     // The path fragment to append after the client ID for updating the metadata URI.
     private static final String QUERY_PATH_METADATA = "metadata";
     private static final String INSERT_METADATA_CLIENT_ID_COLUMN = "clientid";
@@ -156,7 +158,7 @@
                 c.close();
                 return Collections.<WordListInfo>emptyList();
             }
-            final List<WordListInfo> list = CollectionUtils.newArrayList();
+            final ArrayList<WordListInfo> list = CollectionUtils.newArrayList();
             do {
                 final String wordListId = c.getString(0);
                 final String wordListLocale = c.getString(1);
@@ -186,13 +188,18 @@
     /**
      * Helper method to encapsulate exception handling.
      */
-    private static AssetFileDescriptor openAssetFileDescriptor(final ContentResolver resolver,
-            final Uri uri) {
+    private static AssetFileDescriptor openAssetFileDescriptor(
+            final ContentProviderClient providerClient, final Uri uri) {
         try {
-            return resolver.openAssetFileDescriptor(uri, "r");
+            return providerClient.openAssetFile(uri, "r");
         } catch (FileNotFoundException e) {
-            // I don't want to log the word list URI here for security concerns
-            Log.e(TAG, "Could not find a word list from the dictionary provider.");
+            // I don't want to log the word list URI here for security concerns. The exception
+            // contains the name of the file, so let's not pass it to Log.e here.
+            Log.e(TAG, "Could not find a word list from the dictionary provider."
+                    /* intentionally don't pass the exception (see comment above) */);
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't communicate with the dictionary pack", e);
             return null;
         }
     }
@@ -202,9 +209,8 @@
      * to the cache file name designated by its id and locale, overwriting it if already present
      * and creating it (and its containing directory) if necessary.
      */
-    private static AssetFileAddress cacheWordList(final String id, final String locale,
-            final ContentResolver resolver, final Context context) {
-
+    private static AssetFileAddress cacheWordList(final String wordlistId, final String locale,
+            final ContentProviderClient providerClient, final Context context) {
         final int COMPRESSED_CRYPTED_COMPRESSED = 0;
         final int CRYPTED_COMPRESSED = 1;
         final int COMPRESSED_CRYPTED = 2;
@@ -214,11 +220,20 @@
         final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED;
         final int MODE_MAX = NONE;
 
-        final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id);
-        final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
+        final String clientId = context.getString(R.string.dictionary_pack_client_id);
+        final Uri.Builder wordListUriBuilder;
+        try {
+            wordListUriBuilder = getContentUriBuilderForType(clientId,
+                    providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't communicate with the dictionary pack", e);
+            return null;
+        }
+        final String finalFileName =
+                DictionaryInfoUtils.getCacheFileName(wordlistId, locale, context);
         String tempFileName;
         try {
-            tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+            tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context);
         } catch (IOException e) {
             Log.e(TAG, "Can't open the temporary file", e);
             return null;
@@ -236,7 +251,7 @@
             final Uri wordListUri = wordListUriBuilder.build();
             try {
                 // Open input.
-                afd = openAssetFileDescriptor(resolver, wordListUri);
+                afd = openAssetFileDescriptor(providerClient, wordListUri);
                 // If we can't open it at all, don't even try a number of times.
                 if (null == afd) return null;
                 originalSourceStream = afd.createInputStream();
@@ -284,10 +299,10 @@
                 }
                 wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
                         QUERY_PARAMETER_SUCCESS);
-                if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
+                if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
                     Log.e(TAG, "Could not have the dictionary pack delete a word list");
                 }
-                BinaryDictionaryGetter.removeFilesWithIdExcept(context, id, finalFile);
+                BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
                 // Success! Close files (through the finally{} clause) and return.
                 return AssetFileAddress.makeFromFileName(finalFileName);
             } catch (Exception e) {
@@ -327,8 +342,12 @@
         // as invalid.
         wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
                 QUERY_PARAMETER_FAILURE);
-        if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
-            Log.e(TAG, "In addition, we were unable to delete it.");
+        try {
+            if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
+                Log.e(TAG, "In addition, we were unable to delete it.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
         }
         return null;
     }
@@ -345,17 +364,27 @@
      */
     public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale,
             final Context context, final boolean hasDefaultWordList) {
-        final ContentResolver resolver = context.getContentResolver();
-        final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
-                hasDefaultWordList);
-        final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
-        for (WordListInfo id : idList) {
-            final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
-            if (null != afd) {
-                fileAddressList.add(afd);
-            }
+        final ContentProviderClient providerClient = context.getContentResolver().
+                acquireContentProviderClient(getProviderUriBuilder("").build());
+        if (null == providerClient) {
+            Log.e(TAG, "Can't establish communication with the dictionary provider");
+            return CollectionUtils.newArrayList();
         }
-        return fileAddressList;
+        try {
+            final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
+                    hasDefaultWordList);
+            final ArrayList<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
+            for (WordListInfo id : idList) {
+                final AssetFileAddress afd =
+                        cacheWordList(id.mId, id.mLocale, providerClient, context);
+                if (null != afd) {
+                    fileAddressList.add(afd);
+                }
+            }
+            return fileAddressList;
+        } finally {
+            providerClient.release();
+        }
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 31f616d..f7cb434 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 
@@ -177,7 +178,8 @@
     /**
      * Internal method to retrieve reasonable proximity info for a character.
      */
-    private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
+    @UsedForTesting
+    public void addKeyInfo(final int codePoint, final Keyboard keyboard) {
         final int x, y;
         final Key key;
         if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) {
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index fab8945..c30ecfb 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -18,22 +18,323 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.View;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
 
+import com.android.inputmethod.compat.TextViewCompatUtils;
+import com.android.inputmethod.compat.ViewCompatUtils;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.SettingsActivity;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+
+import java.util.HashMap;
 
 public final class SetupActivity extends Activity {
+    private SetupStepIndicatorView mStepIndicatorView;
+    private final SetupStepGroup mSetupSteps = new SetupStepGroup();
+    private static final String STATE_STEP = "step";
+    private int mStepNo;
+    private static final int STEP_1 = 1;
+    private static final int STEP_2 = 2;
+    private static final int STEP_3 = 3;
+
+    private final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this);
+
+    static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupActivity> {
+        private static final int MSG_POLLING_IME_SETTINGS = 0;
+        private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
+
+        public SettingsPoolingHandler(final SetupActivity outerInstance) {
+            super(outerInstance);
+        }
+
+        @Override
+        public void handleMessage(final Message msg) {
+            final SetupActivity setupActivity = getOuterInstance();
+            switch (msg.what) {
+            case MSG_POLLING_IME_SETTINGS:
+                if (setupActivity.isMyImeEnabled()) {
+                    setupActivity.invokeSetupWizardOfThisIme();
+                    return;
+                }
+                startPollingImeSettings();
+                break;
+            }
+        }
+
+        public void startPollingImeSettings() {
+            sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS),
+                    IME_SETTINGS_POLLING_INTERVAL);
+        }
+
+        public void cancelPollingImeSettings() {
+            removeMessages(MSG_POLLING_IME_SETTINGS);
+        }
+    }
+
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
+    protected void onCreate(final Bundle savedInstanceState) {
+        setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar);
         super.onCreate(savedInstanceState);
 
-        // TODO: Implement setup wizard.
+        setContentView(R.layout.setup_wizard);
+
+        RichInputMethodManager.init(this);
+
+        if (savedInstanceState == null) {
+            mStepNo = determineSetupStepNo();
+        } else {
+            mStepNo = savedInstanceState.getInt(STATE_STEP);
+        }
+
+        if (mStepNo == STEP_3) {
+            // This IME already has been enabled and set as current IME.
+            // TODO: Implement tutorial.
+            invokeSettingsOfThisIme();
+            finish();
+            return;
+        }
+
+        // TODO: Use sans-serif-thin font family depending on the system locale white list and
+        // the SDK version.
+        final TextView titleView = (TextView)findViewById(R.id.setup_title);
+        titleView.setText(getString(R.string.setup_title, getString(R.string.english_ime_name)));
+
+        mStepIndicatorView = (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator);
+
+        final SetupStep step1 = new SetupStep(findViewById(R.id.setup_step1),
+                R.string.setup_step1_title, R.string.setup_step1_instruction,
+                R.drawable.ic_settings_language, R.string.language_settings);
+        step1.setAction(new Runnable() {
+            @Override
+            public void run() {
+                invokeLanguageAndInputSettings();
+                mHandler.startPollingImeSettings();
+            }
+        });
+        mSetupSteps.addStep(STEP_1, step1);
+
+        final SetupStep step2 = new SetupStep(findViewById(R.id.setup_step2),
+                R.string.setup_step2_title, R.string.setup_step2_instruction,
+                0 /* actionIcon */, R.string.select_input_method);
+        step2.setAction(new Runnable() {
+            @Override
+            public void run() {
+                // Invoke input method picker.
+                RichInputMethodManager.getInstance().getInputMethodManager()
+                        .showInputMethodPicker();
+            }
+        });
+        mSetupSteps.addStep(STEP_2, step2);
+
+        final SetupStep step3 = new SetupStep(findViewById(R.id.setup_step3),
+                R.string.setup_step3_title, 0 /* instruction */,
+                R.drawable.sym_keyboard_language_switch, R.string.setup_step3_instruction);
+        step3.setAction(new Runnable() {
+            @Override
+            public void run() {
+                invokeSubtypeEnablerOfThisIme();
+            }
+        });
+        mSetupSteps.addStep(STEP_3, step3);
+    }
+
+    private void invokeSetupWizardOfThisIme() {
         final Intent intent = new Intent();
-        intent.setClass(this, SettingsActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+        intent.setClass(this, SetupActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         startActivity(intent);
-        finish();
+    }
+
+    private void invokeSettingsOfThisIme() {
+        final Intent intent = new Intent();
+        intent.setClass(this, SettingsActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        startActivity(intent);
+    }
+
+    private void invokeLanguageAndInputSettings() {
+        final Intent intent = new Intent();
+        intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        startActivity(intent);
+    }
+
+    private void invokeSubtypeEnablerOfThisIme() {
+        final InputMethodInfo imi =
+                RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+        final Intent intent = new Intent();
+        intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId());
+        startActivity(intent);
+    }
+
+    private boolean isMyImeEnabled() {
+        final String packageName = getPackageName();
+        final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager();
+        for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+            if (packageName.equals(imi.getPackageName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isMyImeCurrent() {
+        final InputMethodInfo myImi =
+                RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+        final String currentImeId = Settings.Secure.getString(
+                getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        return myImi.getId().equals(currentImeId);
+    }
+
+    private int determineSetupStepNo() {
+        mHandler.cancelPollingImeSettings();
+        if (!isMyImeEnabled()) {
+            return STEP_1;
+        }
+        if (!isMyImeCurrent()) {
+            return STEP_2;
+        }
+        return STEP_3;
+    }
+
+    @Override
+    protected void onSaveInstanceState(final Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(STATE_STEP, mStepNo);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        mStepNo = savedInstanceState.getInt(STATE_STEP);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mStepNo = determineSetupStepNo();
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        mStepNo = determineSetupStepNo();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        updateSetupStepView();
+    }
+
+    @Override
+    public void onWindowFocusChanged(final boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (!hasFocus) {
+            return;
+        }
+        mStepNo = determineSetupStepNo();
+        updateSetupStepView();
+    }
+
+    private void updateSetupStepView() {
+        final int layoutDirection = ViewCompatUtils.getLayoutDirection(mStepIndicatorView);
+        mStepIndicatorView.setIndicatorPosition(
+                getIndicatorPosition(mStepNo, mSetupSteps.getTotalStep(), layoutDirection));
+        mSetupSteps.enableStep(mStepNo);
+    }
+
+    private static float getIndicatorPosition(final int step, final int totalStep,
+            final int layoutDirection) {
+        final float pos = ((step - STEP_1) * 2 + 1) / (float)(totalStep * 2);
+        return (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
+    }
+
+    static final class SetupStep implements View.OnClickListener {
+        private final View mRootView;
+        private final TextView mActionLabel;
+        private Runnable mAction;
+
+        public SetupStep(final View rootView, final int title, final int instruction,
+                final int actionIcon, final int actionLabel) {
+            mRootView = rootView;
+            final Resources res = rootView.getResources();
+            final String applicationName = res.getString(R.string.english_ime_name);
+
+            final TextView titleView = (TextView)rootView.findViewById(R.id.setup_step_title);
+            titleView.setText(res.getString(title, applicationName));
+
+            final TextView instructionView = (TextView)rootView.findViewById(
+                    R.id.setup_step_instruction);
+            if (instruction == 0) {
+                instructionView.setVisibility(View.GONE);
+            } else {
+                instructionView.setText(res.getString(instruction, applicationName));
+            }
+
+            mActionLabel = (TextView)rootView.findViewById(R.id.setup_step_action_label);
+            mActionLabel.setText(res.getString(actionLabel));
+            if (actionIcon == 0) {
+                final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel);
+                ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0);
+            } else {
+                final int overrideColor = res.getColor(R.color.setup_text_action);
+                final Drawable icon = res.getDrawable(actionIcon);
+                icon.setColorFilter(overrideColor, PorterDuff.Mode.MULTIPLY);
+                icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+                TextViewCompatUtils.setCompoundDrawablesRelative(
+                        mActionLabel, icon, null, null, null);
+            }
+        }
+
+        public void setEnabled(final boolean enabled) {
+            mRootView.setVisibility(enabled ? View.VISIBLE : View.GONE);
+        }
+
+        public void setAction(final Runnable action) {
+            mActionLabel.setOnClickListener(this);
+            mAction = action;
+        }
+
+        @Override
+        public void onClick(final View v) {
+            if (mAction != null) {
+                mAction.run();
+            }
+        }
+    }
+
+    static final class SetupStepGroup {
+        private final HashMap<Integer, SetupStep> mGroup = CollectionUtils.newHashMap();
+
+        public void addStep(final int stepNo, final SetupStep step) {
+            mGroup.put(stepNo, step);
+        }
+
+        public void enableStep(final int enableStepNo) {
+            for (final Integer stepNo : mGroup.keySet()) {
+                final SetupStep step = mGroup.get(stepNo);
+                step.setEnabled(stepNo == enableStepNo);
+            }
+        }
+
+        public int getTotalStep() {
+            return mGroup.size();
+        }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
new file mode 100644
index 0000000..077a217
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.setup;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.inputmethod.latin.R;
+
+public final class SetupStepIndicatorView extends View {
+    private final Path mIndicatorPath = new Path();
+    private final Paint mIndicatorPaint = new Paint();
+    private float mXRatio;
+
+    public SetupStepIndicatorView(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+        mIndicatorPaint.setColor(getResources().getColor(R.color.setup_step_background));
+        mIndicatorPaint.setStyle(Paint.Style.FILL);
+    }
+
+    public void setIndicatorPosition(final float xRatio) {
+        mXRatio = xRatio;
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(final Canvas canvas) {
+        super.onDraw(canvas);
+        final int xPos = (int)(getWidth() * mXRatio);
+        final int height = getHeight();
+        mIndicatorPath.rewind();
+        mIndicatorPath.moveTo(xPos, 0);
+        mIndicatorPath.lineTo(xPos + height, height);
+        mIndicatorPath.lineTo(xPos - height, height);
+        mIndicatorPath.close();
+        canvas.drawPath(mIndicatorPath, mIndicatorPaint);
+    }
+}
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index a1fdae7..a4edf91 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -214,6 +214,8 @@
 #define DEBUG_SAMPLING_POINTS false
 #define DEBUG_POINTS_PROBABILITY false
 #define DEBUG_DOUBLE_LETTER false
+#define DEBUG_CACHE false
+#define DEBUG_DUMP_ERROR false
 
 #ifdef FLAG_FULL_DBG
 #define DEBUG_GEO_FULL true
@@ -237,6 +239,8 @@
 #define DEBUG_SAMPLING_POINTS false
 #define DEBUG_POINTS_PROBABILITY false
 #define DEBUG_DOUBLE_LETTER false
+#define DEBUG_CACHE false
+#define DEBUG_DUMP_ERROR false
 
 #define DEBUG_GEO_FULL false
 
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index bdbf8b1..d13248c 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -36,6 +36,9 @@
     mIsContinuationPossible = ProximityInfoStateUtils::checkAndReturnIsContinuationPossible(
             inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, &mSampledInputXs,
             &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
+    if (DEBUG_DICT) {
+        AKLOGI("isContinuationPossible = %s", (mIsContinuationPossible ? "true" : "false"));
+    }
 
     mProximityInfo = proximityInfo;
     mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();