Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index bab612b..fdc8581 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -71,7 +71,7 @@
 
     <!-- Option to enable sliding key input indicator. The user can see a rubber band-like effect during sliding key input. [CHAR LIMIT=30]-->
     <string name="sliding_key_input_preview">Show slide indicator</string>
-    <!-- Option summary to enable sliding key input indicator. The user can see a rubber band-like effect during sliding key input. [CHAR LIMIT=65]-->
+    <!-- Option summary to enable sliding key input indicator. The user can see a rubber band-like effect during sliding key input. [CHAR LIMIT=66]-->
     <string name="sliding_key_input_preview_summary">Display visual cue while sliding from Shift or Symbol keys</string>
 
     <!-- Option for the dismiss delay of the key popup [CHAR LIMIT=25] -->
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
index a82103a..5a2b6bd 100644
--- a/java/src/com/android/inputmethod/compat/CompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.compat;
 
-import android.content.Intent;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -26,23 +25,9 @@
 
 public final class CompatUtils {
     private static final String TAG = CompatUtils.class.getSimpleName();
-    private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
-    // TODO: Can these be constants instead of literal String constants?
-    private static final String INPUT_METHOD_SUBTYPE_SETTINGS =
-            "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
 
-    public static Intent getInputLanguageSelectionIntent(final String inputMethodId,
-            final int flagsForSubtypeSettings) {
-        // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
-        final String action = INPUT_METHOD_SUBTYPE_SETTINGS;
-        final Intent intent = new Intent(action);
-        if (!TextUtils.isEmpty(inputMethodId)) {
-            intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId);
-        }
-        if (flagsForSubtypeSettings > 0) {
-            intent.setFlags(flagsForSubtypeSettings);
-        }
-        return intent;
+    private CompatUtils() {
+        // This utility class is not publicly instantiable.
     }
 
     public static Class<?> getClass(final String className) {
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index f787722..ff5e339 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -44,8 +44,6 @@
 import android.widget.SpinnerAdapter;
 import android.widget.Toast;
 
-import com.android.inputmethod.compat.CompatUtils;
-
 import java.util.ArrayList;
 import java.util.TreeSet;
 
@@ -519,7 +517,7 @@
                 .setPositiveButton(R.string.enable, new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
-                        final Intent intent = CompatUtils.getInputLanguageSelectionIntent(
+                        final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
                                 mRichImm.getInputMethodIdOfThisIme(),
                                 Intent.FLAG_ACTIVITY_NEW_TASK
                                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
diff --git a/java/src/com/android/inputmethod/latin/IntentUtils.java b/java/src/com/android/inputmethod/latin/IntentUtils.java
new file mode 100644
index 0000000..d175af5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/IntentUtils.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import android.content.Intent;
+import android.text.TextUtils;
+
+public final class IntentUtils {
+    private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    // TODO: Can these be constants instead of literal String constants?
+    private static final String INPUT_METHOD_SUBTYPE_SETTINGS =
+            "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
+
+    private IntentUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static Intent getInputLanguageSelectionIntent(final String inputMethodId,
+            final int flagsForSubtypeSettings) {
+        // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+        final String action = INPUT_METHOD_SUBTYPE_SETTINGS;
+        final Intent intent = new Intent(action);
+        if (!TextUtils.isEmpty(inputMethodId)) {
+            intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId);
+        }
+        if (flagsForSubtypeSettings > 0) {
+            intent.setFlags(flagsForSubtypeSettings);
+        }
+        return intent;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0821732..1c49bb0 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -62,7 +62,6 @@
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.CompatUtils;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.compat.SuggestionSpanUtils;
 import com.android.inputmethod.event.EventInterpreter;
@@ -2512,7 +2511,7 @@
                 di.dismiss();
                 switch (position) {
                 case 0:
-                    Intent intent = CompatUtils.getInputLanguageSelectionIntent(
+                    final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
                             mRichImm.getInputMethodIdOfThisIme(),
                             Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java
index 69ddf82..39f9c87 100644
--- a/java/src/com/android/inputmethod/research/FeedbackFragment.java
+++ b/java/src/com/android/inputmethod/research/FeedbackFragment.java
@@ -36,8 +36,8 @@
 public class FeedbackFragment extends Fragment implements OnClickListener {
     private static final String TAG = FeedbackFragment.class.getSimpleName();
 
-    private static final String KEY_FEEDBACK_STRING = "FeedbackString";
-    private static final String KEY_INCLUDE_ACCOUNT_NAME = "IncludeAccountName";
+    public static final String KEY_FEEDBACK_STRING = "FeedbackString";
+    public static final String KEY_INCLUDE_ACCOUNT_NAME = "IncludeAccountName";
     public static final String KEY_HAS_USER_RECORDING = "HasRecording";
 
     private EditText mEditText;
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
index 26a1d7f..e59adfa 100644
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ b/java/src/com/android/inputmethod/research/MotionEventReader.java
@@ -19,6 +19,8 @@
 import android.util.JsonReader;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
 
 import com.android.inputmethod.latin.define.ProductionFlag;
 
@@ -33,6 +35,14 @@
 public class MotionEventReader {
     private static final String TAG = MotionEventReader.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+    // Assumes that MotionEvent.ACTION_MASK does not have all bits set.`
+    private static final int UNINITIALIZED_ACTION = ~MotionEvent.ACTION_MASK;
+    // No legitimate int is negative
+    private static final int UNINITIALIZED_INT = -1;
+    // No legitimate long is negative
+    private static final long UNINITIALIZED_LONG = -1L;
+    // No legitimate float is negative
+    private static final float UNINITIALIZED_FLOAT = -1.0f;
 
     public ReplayData readMotionEventData(final File file) {
         final ReplayData replayData = new ReplayData();
@@ -55,19 +65,82 @@
 
     static class ReplayData {
         final ArrayList<Integer> mActions = new ArrayList<Integer>();
-        final ArrayList<Integer> mXCoords = new ArrayList<Integer>();
-        final ArrayList<Integer> mYCoords = new ArrayList<Integer>();
+        final ArrayList<PointerProperties[]> mPointerPropertiesArrays
+                = new ArrayList<PointerProperties[]>();
+        final ArrayList<PointerCoords[]> mPointerCoordsArrays = new ArrayList<PointerCoords[]>();
         final ArrayList<Long> mTimes = new ArrayList<Long>();
     }
 
-    private void readLogStatement(final JsonReader jsonReader, final ReplayData replayData)
-            throws IOException {
+    /**
+     * Read motion data from a logStatement and store it in {@code replayData}.
+     *
+     * Two kinds of logStatements can be read.  In the first variant, the MotionEvent data is
+     * represented as attributes at the top level like so:
+     *
+     * <pre>
+     * {
+     *   "_ct": 1359590400000,
+     *   "_ut": 4381933,
+     *   "_ty": "MotionEvent",
+     *   "action": "UP",
+     *   "isLoggingRelated": false,
+     *   "x": 100,
+     *   "y": 200
+     * }
+     * </pre>
+     *
+     * In the second variant, there is a separate attribute for the MotionEvent that includes
+     * historical data if present:
+     *
+     * <pre>
+     * {
+     *   "_ct": 135959040000,
+     *   "_ut": 4382702,
+     *   "_ty": "MotionEvent",
+     *   "action": "MOVE",
+     *   "isLoggingRelated": false,
+     *   "motionEvent": {
+     *     "pointerIds": [
+     *       0
+     *     ],
+     *     "xyt": [
+     *       {
+     *         "t": 4382551,
+     *         "d": [
+     *           {
+     *             "x": 141.25,
+     *             "y": 151.8485107421875,
+     *             "toma": 101.82337188720703,
+     *             "tomi": 101.82337188720703,
+     *             "o": 0.0
+     *           }
+     *         ]
+     *       },
+     *       {
+     *         "t": 4382559,
+     *         "d": [
+     *           {
+     *             "x": 140.7266082763672,
+     *             "y": 151.8485107421875,
+     *             "toma": 101.82337188720703,
+     *             "tomi": 101.82337188720703,
+     *             "o": 0.0
+     *           }
+     *         ]
+     *       }
+     *     ]
+     *   }
+     * },
+     * </pre>
+     */
+    /* package for test */ void readLogStatement(final JsonReader jsonReader,
+            final ReplayData replayData) throws IOException {
         String logStatementType = null;
-        Integer actionType = null;
-        Integer x = null;
-        Integer y = null;
-        Long time = null;
-        boolean loggingRelated = false;
+        int actionType = UNINITIALIZED_ACTION;
+        int x = UNINITIALIZED_INT;
+        int y = UNINITIALIZED_INT;
+        long time = UNINITIALIZED_LONG;
+        boolean isLoggingRelated = false;
 
         jsonReader.beginObject();
         while (jsonReader.hasNext()) {
@@ -90,7 +163,18 @@
                     actionType = MotionEvent.ACTION_MOVE;
                 }
             } else if (key.equals("loggingRelated")) {
-                loggingRelated = jsonReader.nextBoolean();
+                isLoggingRelated = jsonReader.nextBoolean();
+            } else if (logStatementType != null && logStatementType.equals("MotionEvent")
+                    && key.equals("motionEvent")) {
+                if (actionType == UNINITIALIZED_ACTION) {
+                    Log.e(TAG, "no actionType assigned in MotionEvent json");
+                }
+                // Second variant of LogStatement.
+                if (isLoggingRelated) {
+                    jsonReader.skipValue();
+                } else {
+                    readEmbeddedMotionEvent(jsonReader, replayData, actionType);
+                }
             } else {
                 if (DEBUG) {
                     Log.w(TAG, "Unknown JSON key in LogStatement: " + key);
@@ -100,14 +184,149 @@
         }
         jsonReader.endObject();
 
-        if (logStatementType != null && time != null && x != null && y != null && actionType != null
-                && logStatementType.equals("MotionEvent")
-                && !loggingRelated) {
-            replayData.mActions.add(actionType);
-            replayData.mXCoords.add(x);
-            replayData.mYCoords.add(y);
-            replayData.mTimes.add(time);
+        if (logStatementType != null && time != UNINITIALIZED_LONG && x != UNINITIALIZED_INT
+                && y != UNINITIALIZED_INT && actionType != UNINITIALIZED_ACTION
+                && logStatementType.equals("MotionEvent") && !isLoggingRelated) {
+            // First variant of LogStatement.
+            final PointerProperties pointerProperties = new PointerProperties();
+            pointerProperties.id = 0;
+            pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+            final PointerProperties[] pointerPropertiesArray = {
+                pointerProperties
+            };
+            final PointerCoords pointerCoords = new PointerCoords();
+            pointerCoords.x = x;
+            pointerCoords.y = y;
+            pointerCoords.pressure = 1.0f;
+            pointerCoords.size = 1.0f;
+            final PointerCoords[] pointerCoordsArray = {
+                pointerCoords
+            };
+            addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
+                    pointerCoordsArray);
         }
     }
 
+    private void readEmbeddedMotionEvent(final JsonReader jsonReader, final ReplayData replayData,
+            final int actionType) throws IOException {
+        jsonReader.beginObject();
+        PointerProperties[] pointerPropertiesArray = null;
+        while (jsonReader.hasNext()) {  // pointerIds/xyt
+            final String name = jsonReader.nextName();
+            if (name.equals("pointerIds")) {
+                pointerPropertiesArray = readPointerProperties(jsonReader);
+            } else if (name.equals("xyt")) {
+                readPointerData(jsonReader, replayData, actionType, pointerPropertiesArray);
+            }
+        }
+        jsonReader.endObject();
+    }
+
+    private PointerProperties[] readPointerProperties(final JsonReader jsonReader)
+            throws IOException {
+        final ArrayList<PointerProperties> pointerPropertiesArrayList =
+                new ArrayList<PointerProperties>();
+        jsonReader.beginArray();
+        while (jsonReader.hasNext()) {
+            final PointerProperties pointerProperties = new PointerProperties();
+            pointerProperties.id = jsonReader.nextInt();
+            pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+            pointerPropertiesArrayList.add(pointerProperties);
+        }
+        jsonReader.endArray();
+        return pointerPropertiesArrayList.toArray(
+                new PointerProperties[pointerPropertiesArrayList.size()]);
+    }
+
+    private void readPointerData(final JsonReader jsonReader, final ReplayData replayData,
+            final int actionType, final PointerProperties[] pointerPropertiesArray)
+            throws IOException {
+        if (pointerPropertiesArray == null) {
+            Log.e(TAG, "PointerIDs must be given before xyt data in json for MotionEvent");
+            jsonReader.skipValue();
+            return;
+        }
+        long time = UNINITIALIZED_LONG;
+        jsonReader.beginArray();
+        while (jsonReader.hasNext()) {  // Array of historical data
+            jsonReader.beginObject();
+            final ArrayList<PointerCoords> pointerCoordsArrayList = new ArrayList<PointerCoords>();
+            while (jsonReader.hasNext()) {  // Time/data object
+                final String name = jsonReader.nextName();
+                if (name.equals("t")) {
+                    time = jsonReader.nextLong();
+                } else if (name.equals("d")) {
+                    jsonReader.beginArray();
+                    while (jsonReader.hasNext()) {  // array of data per pointer
+                        final PointerCoords pointerCoords = readPointerCoords(jsonReader);
+                        if (pointerCoords != null) {
+                            pointerCoordsArrayList.add(pointerCoords);
+                        }
+                    }
+                    jsonReader.endArray();
+                } else {
+                    jsonReader.skipValue();
+                }
+            }
+            jsonReader.endObject();
+            // Data was recorded as historical events, but must be split apart into
+            // separate MotionEvents for replaying
+            if (time != UNINITIALIZED_LONG) {
+                addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
+                        pointerCoordsArrayList.toArray(
+                                new PointerCoords[pointerCoordsArrayList.size()]));
+            } else {
+                Log.e(TAG, "Time not assigned in json for MotionEvent");
+            }
+        }
+        jsonReader.endArray();
+    }
+
+    private PointerCoords readPointerCoords(final JsonReader jsonReader) throws IOException {
+        jsonReader.beginObject();
+        float x = UNINITIALIZED_FLOAT;
+        float y = UNINITIALIZED_FLOAT;
+        while (jsonReader.hasNext()) {  // x,y
+            final String name = jsonReader.nextName();
+            if (name.equals("x")) {
+                x = (float) jsonReader.nextDouble();
+            } else if (name.equals("y")) {
+                y = (float) jsonReader.nextDouble();
+            } else {
+                jsonReader.skipValue();
+            }
+        }
+        jsonReader.endObject();
+
+        if (Float.compare(x, UNINITIALIZED_FLOAT) == 0
+                || Float.compare(y, UNINITIALIZED_FLOAT) == 0) {
+            Log.w(TAG, "missing x or y value in MotionEvent json");
+            return null;
+        }
+        final PointerCoords pointerCoords = new PointerCoords();
+        pointerCoords.x = x;
+        pointerCoords.y = y;
+        pointerCoords.pressure = 1.0f;
+        pointerCoords.size = 1.0f;
+        return pointerCoords;
+    }
+
+    /**
+     * Tests that {@code x} is uninitialized.
+     *
+     * Assumes that {@code x} will never be given a valid value less than 0, and that
+     * UNINITIALIZED_FLOAT is less than 0.0f.
+     */
+    private boolean isUninitializedFloat(final float x) {
+        return x < 0.0f;
+    }
+
+    private void addMotionEventData(final ReplayData replayData, final int actionType,
+            final long time, final PointerProperties[] pointerProperties,
+            final PointerCoords[] pointerCoords) {
+        replayData.mActions.add(actionType);
+        replayData.mTimes.add(time);
+        replayData.mPointerPropertiesArrays.add(pointerProperties);
+        replayData.mPointerCoordsArrays.add(pointerCoords);
+    }
 }
diff --git a/java/src/com/android/inputmethod/research/Replayer.java b/java/src/com/android/inputmethod/research/Replayer.java
index 611abb2..a9b7a9d 100644
--- a/java/src/com/android/inputmethod/research/Replayer.java
+++ b/java/src/com/android/inputmethod/research/Replayer.java
@@ -22,6 +22,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
 
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
@@ -62,7 +64,6 @@
         if (mIsReplaying) {
             return;
         }
-
         mIsReplaying = true;
         final int numActions = replayData.mActions.size();
         if (DEBUG) {
@@ -95,25 +96,36 @@
                 case MSG_MOTION_EVENT:
                     final int index = msg.arg1;
                     final int action = replayData.mActions.get(index);
-                    final int x = replayData.mXCoords.get(index);
-                    final int y = replayData.mYCoords.get(index);
+                    final PointerProperties[] pointerPropertiesArray =
+                            replayData.mPointerPropertiesArrays.get(index);
+                    final PointerCoords[] pointerCoordsArray =
+                            replayData.mPointerCoordsArrays.get(index);
                     final long origTime = replayData.mTimes.get(index);
                     if (action == MotionEvent.ACTION_DOWN) {
                         mOrigDownTime = origTime;
                     }
 
                     final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment,
-                            origTime + timeAdjustment, action, x, y, 0);
+                            origTime + timeAdjustment, action,
+                            pointerPropertiesArray.length, pointerPropertiesArray,
+                            pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0);
                     mainKeyboardView.processMotionEvent(me);
                     me.recycle();
                     break;
                 case MSG_DONE:
                     mIsReplaying = false;
+                    ResearchLogger.getInstance().requestIndicatorRedraw();
                     break;
                 }
             }
         };
 
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                ResearchLogger.getInstance().requestIndicatorRedraw();
+            }
+        });
         for (int i = 0; i < numActions; i++) {
             final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0);
             final long msgTime = replayData.mTimes.get(i) + timeAdjustment;
@@ -130,4 +142,8 @@
             handler.postAtTime(callback, presentDoneTime + 1);
         }
     }
+
+    public boolean isReplaying() {
+        return mIsReplaying;
+    }
 }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index da41001..8fc62ea 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -115,6 +115,12 @@
     // private info.
     private static final boolean LOG_FULL_TEXTVIEW_CONTENTS = false
             && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+    // Whether the feedback dialog preserves the editable text across invocations.  Should be false
+    // for normal research builds so users do not have to delete the same feedback string they
+    // entered earlier.  Should be true for builds internal to a development team so when the text
+    // field holds a channel name, the developer does not have to re-enter it when using the
+    // feedback mechanism to generate multiple tests.
+    private static final boolean FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD = false;
     public static final boolean DEFAULT_USABILITY_STUDY_MODE = false;
     /* package */ static boolean sIsLogging = false;
     private static final int OUTPUT_FORMAT_VERSION = 5;
@@ -140,6 +146,7 @@
     private static final String WHITESPACE_SEPARATORS = " \t\n\r";
     private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
     private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
+    private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
 
     private static final ResearchLogger sInstance = new ResearchLogger();
     private static String sAccountType = null;
@@ -591,12 +598,20 @@
         mFeedbackLogBuffer = null;
         mFeedbackLog = null;
 
-        Intent intent = new Intent();
+        final Intent intent = new Intent();
         intent.setClass(mLatinIME, FeedbackActivity.class);
-        if (mFeedbackDialogBundle != null) {
-            Log.d(TAG, "putting extra in feedbackdialogbundle");
-            intent.putExtras(mFeedbackDialogBundle);
+        if (mFeedbackDialogBundle == null) {
+            // Restore feedback field with channel name
+            final Bundle bundle = new Bundle();
+            bundle.putBoolean(FeedbackFragment.KEY_INCLUDE_ACCOUNT_NAME, true);
+            bundle.putBoolean(FeedbackFragment.KEY_HAS_USER_RECORDING, false);
+            if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
+                final String savedChannelName = mPrefs.getString(PREF_RESEARCH_SAVED_CHANNEL, "");
+                bundle.putString(FeedbackFragment.KEY_FEEDBACK_STRING, savedChannelName);
+            }
+            mFeedbackDialogBundle = bundle;
         }
+        intent.putExtras(mFeedbackDialogBundle);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         latinIME.startActivity(intent);
     }
@@ -787,6 +802,18 @@
                 }
             }, 1000);
         }
+
+        if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
+            // Use feedback string as a channel name to label feedback strings.  Here we record the
+            // string for prepopulating the field next time.
+            final String channelName = feedbackContents;
+            if (mPrefs == null) {
+                return;
+            }
+            final Editor e = mPrefs.edit();
+            e.putString(PREF_RESEARCH_SAVED_CHANNEL, channelName);
+            e.apply();
+        }
     }
 
     public void uploadNow() {
@@ -819,7 +846,8 @@
     }
 
     private boolean isAllowedToLog() {
-        return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
+        return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog
+                && !isReplaying();
     }
 
     public void requestIndicatorRedraw() {
@@ -832,15 +860,30 @@
         mMainKeyboardView.invalidateAllKeys();
     }
 
+    private boolean isReplaying() {
+        return mReplayer.isReplaying();
+    }
+
+    private int getIndicatorColor() {
+        if (isMakingUserRecording()) {
+            return Color.YELLOW;
+        }
+        if (isReplaying()) {
+            return Color.GREEN;
+        }
+        return Color.RED;
+    }
+
     public void paintIndicator(KeyboardView view, Paint paint, Canvas canvas, int width,
             int height) {
         // TODO: Reimplement using a keyboard background image specific to the ResearchLogger
         // and remove this method.
         // The check for MainKeyboardView ensures that the indicator only decorates the main
         // keyboard, not every keyboard.
-        if (IS_SHOWING_INDICATOR && isAllowedToLog() && view instanceof MainKeyboardView) {
+        if (IS_SHOWING_INDICATOR && (isAllowedToLog() || isReplaying())
+                && view instanceof MainKeyboardView) {
             final int savedColor = paint.getColor();
-            paint.setColor(isMakingUserRecording() ? Color.YELLOW : Color.RED);
+            paint.setColor(getIndicatorColor());
             final Style savedStyle = paint.getStyle();
             paint.setStyle(Style.STROKE);
             final float savedStrokeWidth = paint.getStrokeWidth();
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
index 2a244a7..49ef3a2 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.keyboard;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
 
+@SmallTest
 public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase {
     private static final int WIDTH = 10;
     private static final int HEIGHT = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
index e6c76db..93883dc 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.keyboard;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
 
+@SmallTest
 public class MoreKeysKeyboardBuilderTests extends AndroidTestCase {
     private static final int WIDTH = 10;
     private static final int HEIGHT = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
index 05b1376..d0426eb 100644
--- a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.latin.AdditionalSubtype;
@@ -31,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
+@SmallTest
 public class SpacebarTextTests extends AndroidTestCase {
     // Locale to subtypes list.
     private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index 1346c00..cee62ca 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -18,6 +18,7 @@
 
 import android.app.Instrumentation;
 import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.CollectionUtils;
 
@@ -26,6 +27,7 @@
 import java.util.Arrays;
 import java.util.Locale;
 
+@SmallTest
 public class KeySpecParserCsvTests extends InstrumentationTestCase {
     private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
 
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
index 5bba961..a5b61b3 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -21,12 +21,14 @@
 import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.Constants;
 
 import java.util.Arrays;
 import java.util.Locale;
 
+@SmallTest
 public class KeySpecParserTests extends AndroidTestCase {
     private final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
     private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
index 053bcb5..5fbe6d4 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
 public class KeyboardStateMultiTouchTests extends KeyboardStateTestsBase {
     // Chording input in alphabet.
     public void testChordingAlphabet() {
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index 74ff879..637d475 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
 public class KeyboardStateSingleTouchTests extends KeyboardStateTestsBase {
     // Shift key in alphabet.
     public void testShiftAlphabet() {
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
index 2c3e3a5..e7068af 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
@@ -17,7 +17,9 @@
 package com.android.inputmethod.keyboard.internal;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
+@SmallTest
 public class PointerTrackerQueueTests extends AndroidTestCase {
     public static class Element implements PointerTrackerQueue.Element {
         public static int sPhantomUpCount;
diff --git a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
index 6b4d52d..594b5d6 100644
--- a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
+++ b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
@@ -16,9 +16,11 @@
 
 package com.android.inputmethod.latin;
 
+import android.test.suitebuilder.annotation.LargeTest;
 import android.text.style.SuggestionSpan;
 import android.text.style.UnderlineSpan;
 
+@LargeTest
 public class BlueUnderlineTests extends InputTestsBase {
 
     public void testBlueUnderline() {
diff --git a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java b/tests/src/com/android/inputmethod/latin/EditDistanceTests.java
index c053a49..0b7fcbb 100644
--- a/tests/src/com/android/inputmethod/latin/EditDistanceTests.java
+++ b/tests/src/com/android/inputmethod/latin/EditDistanceTests.java
@@ -17,7 +17,9 @@
 package com.android.inputmethod.latin;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
+@SmallTest
 public class EditDistanceTests extends AndroidTestCase {
     @Override
     protected void setUp() throws Exception {
diff --git a/tests/src/com/android/inputmethod/latin/ForgettingCurveTests.java b/tests/src/com/android/inputmethod/latin/ForgettingCurveTests.java
index a2e71c1..3259312 100644
--- a/tests/src/com/android/inputmethod/latin/ForgettingCurveTests.java
+++ b/tests/src/com/android/inputmethod/latin/ForgettingCurveTests.java
@@ -17,7 +17,9 @@
 package com.android.inputmethod.latin;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
+@SmallTest
 public class ForgettingCurveTests extends AndroidTestCase {
     public void testFcToFreq() {
         for (int i = 0; i < Byte.MAX_VALUE; ++i) {
diff --git a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
index 123959b..fd58476 100644
--- a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
@@ -26,6 +27,7 @@
 /**
  * Unit test for FusionDictionary
  */
+@SmallTest
 public class FusionDictionaryTests extends AndroidTestCase {
     public void testFindWordInTree() {
         FusionDictionary dict = new FusionDictionary(new Node(),
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 6412a9d..d7b2407 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.latin;
 
+import android.test.suitebuilder.annotation.LargeTest;
+
+@LargeTest
 public class InputLogicTests extends InputTestsBase {
 
     public void testTypeWord() {
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
index 42823f5..e012f79 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
@@ -16,8 +16,11 @@
 
 package com.android.inputmethod.latin;
 
+import android.test.suitebuilder.annotation.LargeTest;
+
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 
+@LargeTest
 public class InputLogicTestsNonEnglish extends InputTestsBase {
     final String NEXT_WORD_PREDICTION_OPTION = "next_word_prediction";
 
diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
index cc55076..44526a3 100644
--- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.latin;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import java.util.Arrays;
 
+@SmallTest
 public class InputPointersTests extends AndroidTestCase {
     private static final int DEFAULT_CAPACITY = 48;
 
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 10d415b..c440587 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -31,16 +31,12 @@
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 
-import java.util.HashMap;
 import java.util.Locale;
 
 public class InputTestsBase extends ServiceTestCase<LatinIME> {
diff --git a/tests/src/com/android/inputmethod/latin/PunctuationTests.java b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
index 0eb3ba4..38203e8 100644
--- a/tests/src/com/android/inputmethod/latin/PunctuationTests.java
+++ b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
@@ -16,8 +16,11 @@
 
 package com.android.inputmethod.latin;
 
+import android.test.suitebuilder.annotation.LargeTest;
+
 import com.android.inputmethod.latin.R;
 
+@LargeTest
 public class PunctuationTests extends InputTestsBase {
 
     final String NEXT_WORD_PREDICTION_OPTION = "next_word_prediction";
diff --git a/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java b/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java
index 995fc14..7c415df 100644
--- a/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java
+++ b/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java
@@ -17,7 +17,9 @@
 package com.android.inputmethod.latin;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
+@SmallTest
 public class ResizableIntArrayTests extends AndroidTestCase {
     private static final int DEFAULT_CAPACITY = 48;
 
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
index ad99379..9b8fc6d 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
@@ -18,6 +18,7 @@
 
 import android.inputmethodservice.InputMethodService;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
@@ -25,6 +26,7 @@
 
 import com.android.inputmethod.latin.RichInputConnection.Range;
 
+@SmallTest
 public class RichInputConnectionTests extends AndroidTestCase {
 
     // The following is meant to be a reasonable default for
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 3142bd6..f3fbb3e 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -17,10 +17,12 @@
 package com.android.inputmethod.latin;
 
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 
 import java.util.Locale;
 
+@SmallTest
 public class StringUtilsTests extends AndroidTestCase {
     public void testContainsInArray() {
         assertFalse("empty array", StringUtils.containsInArray("key", new String[0]));
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index e37fef7..e406e84 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
@@ -26,6 +27,7 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
+@SmallTest
 public class SubtypeLocaleTests extends AndroidTestCase {
     // Locale to subtypes list.
     private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
index 66c9e18..dd28fab 100644
--- a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
@@ -16,17 +16,17 @@
 
 package com.android.inputmethod.latin;
 
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
 import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface;
 import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
 
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -39,6 +39,7 @@
 /**
  * Unit tests for UserHistoryDictIOUtils
  */
+@LargeTest
 public class UserHistoryDictIOUtilsTests extends AndroidTestCase
     implements BigramDictionaryInterface {
 
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
index 7536f64..594ba0e 100644
--- a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
@@ -16,15 +16,13 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.latin.UserHistoryDictionary;
-
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
@@ -33,6 +31,7 @@
 /**
  * Unit tests for UserHistoryDictionary
  */
+@LargeTest
 public class UserHistoryDictionaryTests extends AndroidTestCase {
     private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName();
     private SharedPreferences mPrefs;
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index d080ed6..f3694ea 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -16,6 +16,12 @@
 
 package com.android.inputmethod.latin.makedict;
 
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.util.SparseArray;
+
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.UserHistoryDictIOUtils;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
@@ -24,11 +30,6 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-import android.util.Log;
-import android.util.SparseArray;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -47,6 +48,7 @@
 /**
  * Unit tests for BinaryDictInputOutput
  */
+@LargeTest
 public class BinaryDictIOTests extends AndroidTestCase {
     private static final String TAG = BinaryDictIOTests.class.getSimpleName();
     private static final int MAX_UNIGRAMS = 1000;
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index 3185168..25575ba 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -16,32 +16,30 @@
 
 package com.android.inputmethod.latin.makedict;
 
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.ByteBufferWrapper;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-import android.util.Log;
-
 import java.io.BufferedOutputStream;
-import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Random;
 
+@LargeTest
 public class BinaryDictIOUtilsTests  extends AndroidTestCase {
     private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName();
     private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
diff --git a/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java b/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java
index 21406d3..1ea6b2e 100644
--- a/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java
+++ b/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java
@@ -16,12 +16,12 @@
 
 package com.android.inputmethod.latin.spellcheck;
 
-import android.text.SpannableStringBuilder;
-import android.text.style.CharacterStyle;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.text.style.SuggestionSpan;
 
 import com.android.inputmethod.latin.InputTestsBase;
 
+@LargeTest
 public class AndroidSpellCheckerServiceTest extends InputTestsBase {
     public void testSpellchecker() {
         mTextView.onAttachedToWindow();
diff --git a/tests/src/com/android/inputmethod/research/MotionEventReaderTests.java b/tests/src/com/android/inputmethod/research/MotionEventReaderTests.java
new file mode 100644
index 0000000..e5b9bc0
--- /dev/null
+++ b/tests/src/com/android/inputmethod/research/MotionEventReaderTests.java
@@ -0,0 +1,171 @@
+/*
+ * 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.JsonReader;
+
+import com.android.inputmethod.research.MotionEventReader.ReplayData;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+@SmallTest
+public class MotionEventReaderTests extends AndroidTestCase {
+    private MotionEventReader mMotionEventReader = new MotionEventReader();
+    private ReplayData mReplayData;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mReplayData = new ReplayData();
+    }
+
+    private JsonReader jsonReaderForString(final String s) {
+        return new JsonReader(new StringReader(s));
+    }
+
+    public void testTopLevelDataVariant() {
+        final JsonReader jsonReader = jsonReaderForString(
+                "{"
+                + "\"_ct\": 1359590400000,"
+                + "\"_ut\": 4381933,"
+                + "\"_ty\": \"MotionEvent\","
+                + "\"action\": \"UP\","
+                + "\"isLoggingRelated\": false,"
+                + "\"x\": 100.0,"
+                + "\"y\": 200.0"
+                + "}"
+                );
+        try {
+            mMotionEventReader.readLogStatement(jsonReader, mReplayData);
+        } catch (IOException e) {
+            e.printStackTrace();
+            fail("IOException thrown");
+        }
+        assertEquals("x set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100);
+        assertEquals("y set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200);
+        assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1);
+        assertEquals("only one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1);
+    }
+
+    public void testNestedDataVariant() {
+        final JsonReader jsonReader = jsonReaderForString(
+                "{"
+                + "  \"_ct\": 135959040000,"
+                + "  \"_ut\": 4382702,"
+                + "  \"_ty\": \"MotionEvent\","
+                + "  \"action\": \"MOVE\","
+                + "  \"isLoggingRelated\": false,"
+                + "  \"motionEvent\": {"
+                + "    \"pointerIds\": ["
+                + "      0"
+                + "    ],"
+                + "    \"xyt\": ["
+                + "      {"
+                + "        \"t\": 4382551,"
+                + "        \"d\": ["
+                + "          {"
+                + "            \"x\": 100.0,"
+                + "            \"y\": 200.0,"
+                + "            \"toma\": 999.0,"
+                + "            \"tomi\": 999.0,"
+                + "            \"o\": 0.0"
+                + "          }"
+                + "        ]"
+                + "      },"
+                + "      {"
+                + "        \"t\": 4382559,"
+                + "        \"d\": ["
+                + "          {"
+                + "            \"x\": 300.0,"
+                + "            \"y\": 400.0,"
+                + "            \"toma\": 999.0,"
+                + "            \"tomi\": 999.0,"
+                + "            \"o\": 0.0"
+                + "          }"
+                + "        ]"
+                + "      }"
+                + "    ]"
+                + "  }"
+                + "}"
+                );
+        try {
+            mMotionEventReader.readLogStatement(jsonReader, mReplayData);
+        } catch (IOException e) {
+            e.printStackTrace();
+            fail("IOException thrown");
+        }
+        assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100);
+        assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200);
+        assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].x, 300);
+        assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].y, 400);
+        assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1);
+        assertEquals("two MotionEvents", mReplayData.mPointerCoordsArrays.size(), 2);
+    }
+
+    public void testNestedDataVariantMultiPointer() {
+        final JsonReader jsonReader = jsonReaderForString(
+                "{"
+                + "  \"_ct\": 135959040000,"
+                + "  \"_ut\": 4382702,"
+                + "  \"_ty\": \"MotionEvent\","
+                + "  \"action\": \"MOVE\","
+                + "  \"isLoggingRelated\": false,"
+                + "  \"motionEvent\": {"
+                + "    \"pointerIds\": ["
+                + "      1"
+                + "    ],"
+                + "    \"xyt\": ["
+                + "      {"
+                + "        \"t\": 4382551,"
+                + "        \"d\": ["
+                + "          {"
+                + "            \"x\": 100.0,"
+                + "            \"y\": 200.0,"
+                + "            \"toma\": 999.0,"
+                + "            \"tomi\": 999.0,"
+                + "            \"o\": 0.0"
+                + "          },"
+                + "          {"
+                + "            \"x\": 300.0,"
+                + "            \"y\": 400.0,"
+                + "            \"toma\": 999.0,"
+                + "            \"tomi\": 999.0,"
+                + "            \"o\": 0.0"
+                + "          }"
+                + "        ]"
+                + "      }"
+                + "    ]"
+                + "  }"
+                + "}"
+                );
+        try {
+            mMotionEventReader.readLogStatement(jsonReader, mReplayData);
+        } catch (IOException e) {
+            e.printStackTrace();
+            fail("IOException thrown");
+        }
+        assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100);
+        assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200);
+        assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].x, 300);
+        assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].y, 400);
+        assertEquals("two pointers", mReplayData.mPointerCoordsArrays.get(0).length, 2);
+        assertEquals("one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1);
+    }
+}