Merge "Use common incremental decoder output scale."
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/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/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/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index 1a9a720..e91976a 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -151,10 +151,10 @@
                     continue;
                 }
                 // Only retrieve the jsonWriter if we need to.  If we don't get this far, then
-                // researchLog.getValidJsonWriterLocked() will not ever be called, and the file
-                // will not have been opened for writing.
+                // researchLog.getInitializedJsonWriterLocked() will not ever be called, and the
+                // file will not have been opened for writing.
                 if (jsonWriter == null) {
-                    jsonWriter = researchLog.getValidJsonWriterLocked();
+                    jsonWriter = researchLog.getInitializedJsonWriterLocked();
                     outputLogUnitStart(jsonWriter, canIncludePrivateData);
                 }
                 logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
diff --git a/java/src/com/android/inputmethod/research/LoggingUtils.java b/java/src/com/android/inputmethod/research/LoggingUtils.java
new file mode 100644
index 0000000..1261d67
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LoggingUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.research;
+
+import android.view.MotionEvent;
+
+/* package */ class LoggingUtils {
+    private LoggingUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    /* package */ static String getMotionEventActionTypeString(final int actionType) {
+        switch (actionType) {
+        case MotionEvent.ACTION_CANCEL: return "CANCEL";
+        case MotionEvent.ACTION_UP: return "UP";
+        case MotionEvent.ACTION_DOWN: return "DOWN";
+        case MotionEvent.ACTION_POINTER_UP: return "POINTER_UP";
+        case MotionEvent.ACTION_POINTER_DOWN: return "POINTER_DOWN";
+        case MotionEvent.ACTION_MOVE: return "MOVE";
+        case MotionEvent.ACTION_OUTSIDE: return "OUTSIDE";
+        default: return "ACTION_" + actionType;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 3a87bf1..45b83dd 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -64,15 +64,6 @@
     // The size of the n-grams logged.  E.g. N_GRAM_SIZE = 2 means to sample bigrams.
     public static final int N_GRAM_SIZE = 2;
 
-    // Whether all words should be recorded, leaving unsampled word between bigrams.  Useful for
-    // testing.
-    /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false
-            && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
-
-    // The number of words between n-grams to omit from the log.
-    private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES =
-            IS_LOGGING_EVERYTHING ? 0 : (DEBUG ? 2 : 18);
-
     private Suggest mSuggest;
     private boolean mIsStopping = false;
 
@@ -82,17 +73,21 @@
     // after a sample is taken.
     /* package for test */ int mNumWordsUntilSafeToSample;
 
-    public MainLogBuffer() {
-        super(N_GRAM_SIZE + DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES);
-        mNumWordsBetweenNGrams = DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES;
-        final Random random = new Random();
-        mNumWordsUntilSafeToSample = DEBUG ? 0 : random.nextInt(mNumWordsBetweenNGrams + 1);
+    public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore) {
+        super(N_GRAM_SIZE + wordsBetweenSamples);
+        mNumWordsBetweenNGrams = wordsBetweenSamples;
+        mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
     }
 
     public void setSuggest(final Suggest suggest) {
         mSuggest = suggest;
     }
 
+    private Dictionary getDictionary() {
+        if (mSuggest == null || !mSuggest.hasMainDictionary()) return null;
+        return mSuggest.getMainDictionary();
+    }
+
     public void resetWordCounter() {
         mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams;
     }
@@ -114,7 +109,7 @@
      */
     private boolean isSafeNGram(final ArrayList<LogUnit> logUnits, final int minNGramSize) {
         // Bypass privacy checks when debugging.
-        if (IS_LOGGING_EVERYTHING) {
+        if (ResearchLogger.IS_LOGGING_EVERYTHING) {
             if (mIsStopping) {
                 return true;
             }
@@ -137,18 +132,15 @@
         if (mNumWordsUntilSafeToSample > 0) {
             return false;
         }
-        if (mSuggest == null || !mSuggest.hasMainDictionary()) {
+        // Reload the dictionary in case it has changed (e.g., because the user has changed
+        // languages).
+        final Dictionary dictionary = getDictionary();
+        if (dictionary == null) {
             // Main dictionary is unavailable.  Since we cannot check it, we cannot tell if a
             // word is out-of-vocabulary or not.  Therefore, we must judge the entire buffer
             // contents to potentially pose a privacy risk.
             return false;
         }
-        // Reload the dictionary in case it has changed (e.g., because the user has changed
-        // languages).
-        final Dictionary dictionary = mSuggest.getMainDictionary();
-        if (dictionary == null) {
-            return false;
-        }
 
         // Check each word in the buffer.  If any word poses a privacy threat, we cannot upload
         // the complete buffer contents in detail.
@@ -220,10 +212,10 @@
             final boolean canIncludePrivateData);
 
     @Override
-    protected void shiftOutWords(int numWords) {
-        int oldNumActualWords = getNumActualWords();
+    protected void shiftOutWords(final int numWords) {
+        final int oldNumActualWords = getNumActualWords();
         super.shiftOutWords(numWords);
-        int numWordsShifted = oldNumActualWords - getNumActualWords();
+        final int numWordsShifted = oldNumActualWords - getNumActualWords();
         mNumWordsUntilSafeToSample -= numWordsShifted;
         if (DEBUG) {
             Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample);
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 5114977..4dff175 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -206,7 +206,7 @@
      * Return a JsonWriter for this ResearchLog.  It is initialized the first time this method is
      * called.  The cached value is returned in future calls.
      */
-    public JsonWriter getValidJsonWriterLocked() {
+    public JsonWriter getInitializedJsonWriterLocked() {
         try {
             if (mJsonWriter == NULL_JSON_WRITER && mFile != null) {
                 final FileOutputStream fos =
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 4521291..25633d63 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -88,6 +88,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Random;
 import java.util.UUID;
 
 /**
@@ -132,13 +133,21 @@
     private static final String USER_RECORDING_FILENAME_SUFFIX = ".txt";
     private static final SimpleDateFormat TIMESTAMP_DATEFORMAT =
             new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
+    // Whether all words should be recorded, leaving unsampled word between bigrams.  Useful for
+    // testing.
+    /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false
+            && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+    // The number of words between n-grams to omit from the log.
+    private static final int NUMBER_OF_WORDS_BETWEEN_SAMPLES =
+            IS_LOGGING_EVERYTHING ? 0 : (DEBUG ? 2 : 18);
+
     // Whether to show an indicator on the screen that logging is on.  Currently a very small red
     // dot in the lower right hand corner.  Most users should not notice it.
     private static final boolean IS_SHOWING_INDICATOR = true;
     // Change the default indicator to something very visible.  Currently two red vertical bars on
     // either side of they keyboard.
     private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false ||
-            (MainLogBuffer.IS_LOGGING_EVERYTHING && ProductionFlag.IS_EXPERIMENTAL_DEBUG);
+            (IS_LOGGING_EVERYTHING && ProductionFlag.IS_EXPERIMENTAL_DEBUG);
     // FEEDBACK_WORD_BUFFER_SIZE should add 1 because it must also hold the feedback LogUnit itself.
     public static final int FEEDBACK_WORD_BUFFER_SIZE = (Integer.MAX_VALUE - 1) + 1;
 
@@ -464,11 +473,12 @@
         }
         if (mMainLogBuffer == null) {
             mMainResearchLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME);
-            mMainLogBuffer = new MainLogBuffer() {
+            final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
+            mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore) {
                 @Override
                 protected void publish(final ArrayList<LogUnit> logUnits,
                         boolean canIncludePrivateData) {
-                    canIncludePrivateData |= MainLogBuffer.IS_LOGGING_EVERYTHING;
+                    canIncludePrivateData |= IS_LOGGING_EVERYTHING;
                     final int length = logUnits.size();
                     for (int i = 0; i < length; i++) {
                         final LogUnit logUnit = logUnits.get(i);
@@ -1190,7 +1200,7 @@
                         Integer.toHexString(editorInfo.inputType),
                         Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
                         Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
-                        OUTPUT_FORMAT_VERSION, MainLogBuffer.IS_LOGGING_EVERYTHING,
+                        OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING,
                         ProductionFlag.IS_EXPERIMENTAL_DEBUG);
             } catch (NameNotFoundException e) {
                 e.printStackTrace();
@@ -1226,17 +1236,7 @@
     public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action,
             final long eventTime, final int index, final int id, final int x, final int y) {
         if (me != null) {
-            final String actionString;
-            switch (action) {
-                case MotionEvent.ACTION_CANCEL: actionString = "CANCEL"; break;
-                case MotionEvent.ACTION_UP: actionString = "UP"; break;
-                case MotionEvent.ACTION_DOWN: actionString = "DOWN"; break;
-                case MotionEvent.ACTION_POINTER_UP: actionString = "POINTER_UP"; break;
-                case MotionEvent.ACTION_POINTER_DOWN: actionString = "POINTER_DOWN"; break;
-                case MotionEvent.ACTION_MOVE: actionString = "MOVE"; break;
-                case MotionEvent.ACTION_OUTSIDE: actionString = "OUTSIDE"; break;
-                default: actionString = "ACTION_" + action; break;
-            }
+            final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
             final ResearchLogger researchLogger = getInstance();
             researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
                     actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 6986f16..28fe1fe 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