Merge "[Rlog41] ResearchLogger debugging support in UploaderService"
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 0f55607..59a3c99 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -791,6 +791,9 @@
 
     private void cancelBatchInput() {
         sPointerTrackerQueue.cancelAllPointerTracker();
+        if (!sInGesture) {
+            return;
+        }
         sInGesture = false;
         if (DEBUG_LISTENER) {
             Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId));
@@ -1208,9 +1211,7 @@
             printTouchEvent("onCancelEvt:", x, y, eventTime);
         }
 
-        if (sInGesture) {
-            cancelBatchInput();
-        }
+        cancelBatchInput();
         sPointerTrackerQueue.cancelAllPointerTracker();
         sPointerTrackerQueue.releaseAllPointers(eventTime);
         onCancelEventInternal();
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index ba932e5..16ec5b5 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -204,6 +204,7 @@
         case CODE_UNSPECIFIED: return "unspec";
         case CODE_TAB: return "tab";
         case CODE_ENTER: return "enter";
+        case CODE_RESEARCH: return "research";
         default:
             if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
             if (code < 0x100) return String.format("'%c'", code);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f0705a8..7dfaf6c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1681,6 +1681,9 @@
             if (length > 0) {
                 if (mWordComposer.isBatchMode()) {
                     mWordComposer.reset();
+                    if (ProductionFlag.IS_EXPERIMENTAL) {
+                        ResearchLogger.latinIME_handleBackspace_batch(mWordComposer.getTypedWord());
+                    }
                 } else {
                     mWordComposer.deleteLast();
                 }
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index 52c066a..a14398f 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -23,4 +23,9 @@
 
     public static final boolean IS_EXPERIMENTAL = false;
     public static final boolean IS_INTERNAL = false;
+
+    // When false, IS_EXPERIMENTAL_DEBUG suggests that all guarded class-private DEBUG flags should
+    // be false, and any privacy controls should be enforced.  IS_EXPERIMENTAL_DEBUG should be false
+    // for any released build.
+    public static final boolean IS_EXPERIMENTAL_DEBUG = false;
 }
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
new file mode 100644
index 0000000..cb331d7
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/JsonUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 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.content.SharedPreferences;
+import android.util.JsonWriter;
+import android.view.inputmethod.CompletionInfo;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.io.IOException;
+import java.util.Map;
+
+/* package */ class JsonUtils {
+    private JsonUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    /* package */ static void writeJson(final CompletionInfo[] ci, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginArray();
+        for (int j = 0; j < ci.length; j++) {
+            jsonWriter.value(ci[j].toString());
+        }
+        jsonWriter.endArray();
+    }
+
+    /* package */ static void writeJson(final SharedPreferences prefs, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginObject();
+        for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
+            jsonWriter.name(entry.getKey());
+            final Object innerValue = entry.getValue();
+            if (innerValue == null) {
+                jsonWriter.nullValue();
+            } else if (innerValue instanceof Boolean) {
+                jsonWriter.value((Boolean) innerValue);
+            } else if (innerValue instanceof Number) {
+                jsonWriter.value((Number) innerValue);
+            } else {
+                jsonWriter.value(innerValue.toString());
+            }
+        }
+        jsonWriter.endObject();
+    }
+
+    /* package */ static void writeJson(final Key[] keys, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginArray();
+        for (Key key : keys) {
+            writeJson(key, jsonWriter);
+        }
+        jsonWriter.endArray();
+    }
+
+    private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("code").value(key.mCode);
+        jsonWriter.name("altCode").value(key.getAltCode());
+        jsonWriter.name("x").value(key.mX);
+        jsonWriter.name("y").value(key.mY);
+        jsonWriter.name("w").value(key.mWidth);
+        jsonWriter.name("h").value(key.mHeight);
+        jsonWriter.endObject();
+    }
+
+    /* package */ static void writeJson(final SuggestedWords words, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("typedWordValid").value(words.mTypedWordValid);
+        jsonWriter.name("willAutoCorrect")
+                .value(words.mWillAutoCorrect);
+        jsonWriter.name("isPunctuationSuggestions")
+                .value(words.mIsPunctuationSuggestions);
+        jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
+        jsonWriter.name("isPrediction").value(words.mIsPrediction);
+        jsonWriter.name("words");
+        jsonWriter.beginArray();
+        final int size = words.size();
+        for (int j = 0; j < size; j++) {
+            final SuggestedWordInfo wordInfo = words.getWordInfo(j);
+            jsonWriter.value(wordInfo.toString());
+        }
+        jsonWriter.endArray();
+        jsonWriter.endObject();
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index ab9d2f8..884ade0 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -16,10 +16,21 @@
 
 package com.android.inputmethod.research;
 
+import android.content.SharedPreferences;
+import android.util.JsonWriter;
+import android.util.Log;
+import android.view.inputmethod.CompletionInfo;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger.LogStatement;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A group of log statements related to each other.
@@ -36,6 +47,8 @@
  * been published recently, or whether the LogUnit contains numbers, etc.
  */
 /* package */ class LogUnit {
+    private static final String TAG = LogUnit.class.getSimpleName();
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
     private final ArrayList<LogStatement> mLogStatementList;
     private final ArrayList<Object[]> mValuesList;
     // Assume that mTimeList is sorted in increasing order.  Do not insert null values into
@@ -77,8 +90,13 @@
         mTimeList.add(time);
     }
 
-    public void publishTo(final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+    /**
+     * Publish the contents of this LogUnit to researchLog.
+     */
+    public synchronized void publishTo(final ResearchLog researchLog,
+            final boolean isIncludingPrivateData) {
         final int size = mLogStatementList.size();
+        // Write out any logStatement that passes the privacy filter.
         for (int i = 0; i < size; i++) {
             final LogStatement logStatement = mLogStatementList.get(i);
             if (!isIncludingPrivateData && logStatement.mIsPotentiallyPrivate) {
@@ -87,10 +105,72 @@
             if (mIsPartOfMegaword && logStatement.mIsPotentiallyRevealing) {
                 continue;
             }
-            researchLog.outputEvent(mLogStatementList.get(i), mValuesList.get(i), mTimeList.get(i));
+            // Only retrieve the jsonWriter if we need to.  If we don't get this far, then
+            // researchLog.getValidJsonWriter() will not open the file for writing.
+            final JsonWriter jsonWriter = researchLog.getValidJsonWriterLocked();
+            outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i),
+                    mTimeList.get(i));
         }
     }
 
+    private static final String CURRENT_TIME_KEY = "_ct";
+    private static final String UPTIME_KEY = "_ut";
+    private static final String EVENT_TYPE_KEY = "_ty";
+
+    /**
+     * Write the logStatement and its contents out through jsonWriter.
+     *
+     * Note that this method is not thread safe for the same jsonWriter.  Callers must ensure
+     * thread safety.
+     */
+    private boolean outputLogStatementToLocked(final JsonWriter jsonWriter,
+            final LogStatement logStatement, final Object[] values, final Long time) {
+        if (DEBUG) {
+            if (logStatement.mKeys.length != values.length) {
+                Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.mName);
+            }
+        }
+        try {
+            jsonWriter.beginObject();
+            jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
+            jsonWriter.name(UPTIME_KEY).value(time);
+            jsonWriter.name(EVENT_TYPE_KEY).value(logStatement.mName);
+            final String[] keys = logStatement.mKeys;
+            final int length = values.length;
+            for (int i = 0; i < length; i++) {
+                jsonWriter.name(keys[i]);
+                final Object value = values[i];
+                if (value instanceof CharSequence) {
+                    jsonWriter.value(value.toString());
+                } else if (value instanceof Number) {
+                    jsonWriter.value((Number) value);
+                } else if (value instanceof Boolean) {
+                    jsonWriter.value((Boolean) value);
+                } else if (value instanceof CompletionInfo[]) {
+                    JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter);
+                } else if (value instanceof SharedPreferences) {
+                    JsonUtils.writeJson((SharedPreferences) value, jsonWriter);
+                } else if (value instanceof Key[]) {
+                    JsonUtils.writeJson((Key[]) value, jsonWriter);
+                } else if (value instanceof SuggestedWords) {
+                    JsonUtils.writeJson((SuggestedWords) value, jsonWriter);
+                } else if (value == null) {
+                    jsonWriter.nullValue();
+                } else {
+                    Log.w(TAG, "Unrecognized type to be logged: " +
+                            (value == null ? "<null>" : value.getClass().getName()));
+                    jsonWriter.nullValue();
+                }
+            }
+            jsonWriter.endObject();
+        } catch (IOException e) {
+            e.printStackTrace();
+            Log.w(TAG, "Error in JsonWriter; skipping LogStatement");
+            return false;
+        }
+        return true;
+    }
+
     public void setWord(String word) {
         mWord = word;
     }
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index b027643..0185e5f 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -20,18 +20,19 @@
 
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.define.ProductionFlag;
 
 import java.util.Random;
 
 public class MainLogBuffer extends LogBuffer {
     private static final String TAG = MainLogBuffer.class.getSimpleName();
-    // For privacy reasons, be sure to set to "false" for production code.
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
 
     // The size of the n-grams logged.  E.g. N_GRAM_SIZE = 2 means to sample bigrams.
     private static final int N_GRAM_SIZE = 2;
     // The number of words between n-grams to omit from the log.
-    private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = DEBUG ? 2 : 18;
+    private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES =
+            ProductionFlag.IS_EXPERIMENTAL_DEBUG ? 2 : 18;
 
     private final ResearchLog mResearchLog;
     private Suggest mSuggest;
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index f0a1317..a6b1b88 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -16,17 +16,10 @@
 
 package com.android.inputmethod.research;
 
-import android.content.SharedPreferences;
-import android.os.SystemClock;
 import android.util.JsonWriter;
 import android.util.Log;
-import android.view.inputmethod.CompletionInfo;
 
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger.LogStatement;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -34,7 +27,6 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
-import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
@@ -52,7 +44,7 @@
  */
 public class ResearchLog {
     private static final String TAG = ResearchLog.class.getSimpleName();
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
     private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
     private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
 
@@ -204,103 +196,17 @@
         }
     }
 
-    private static final String CURRENT_TIME_KEY = "_ct";
-    private static final String UPTIME_KEY = "_ut";
-    private static final String EVENT_TYPE_KEY = "_ty";
-
-    void outputEvent(final LogStatement logStatement, final Object[] values, final long time) {
-        // Not thread safe.
-        if (DEBUG) {
-            if (logStatement.mKeys.length != values.length) {
-                Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.mName);
-            }
-        }
+    /**
+     * 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() {
         try {
             if (mJsonWriter == NULL_JSON_WRITER) {
                 mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
                 mJsonWriter.beginArray();
                 mHasWrittenData = true;
             }
-            mJsonWriter.beginObject();
-            mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
-            mJsonWriter.name(UPTIME_KEY).value(time);
-            mJsonWriter.name(EVENT_TYPE_KEY).value(logStatement.mName);
-            final String[] keys = logStatement.mKeys;
-            final int length = values.length;
-            for (int i = 0; i < length; i++) {
-                mJsonWriter.name(keys[i]);
-                Object value = values[i];
-                if (value instanceof CharSequence) {
-                    mJsonWriter.value(value.toString());
-                } else if (value instanceof Number) {
-                    mJsonWriter.value((Number) value);
-                } else if (value instanceof Boolean) {
-                    mJsonWriter.value((Boolean) value);
-                } else if (value instanceof CompletionInfo[]) {
-                    CompletionInfo[] ci = (CompletionInfo[]) value;
-                    mJsonWriter.beginArray();
-                    for (int j = 0; j < ci.length; j++) {
-                        mJsonWriter.value(ci[j].toString());
-                    }
-                    mJsonWriter.endArray();
-                } else if (value instanceof SharedPreferences) {
-                    SharedPreferences prefs = (SharedPreferences) value;
-                    mJsonWriter.beginObject();
-                    for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
-                        mJsonWriter.name(entry.getKey());
-                        final Object innerValue = entry.getValue();
-                        if (innerValue == null) {
-                            mJsonWriter.nullValue();
-                        } else if (innerValue instanceof Boolean) {
-                            mJsonWriter.value((Boolean) innerValue);
-                        } else if (innerValue instanceof Number) {
-                            mJsonWriter.value((Number) innerValue);
-                        } else {
-                            mJsonWriter.value(innerValue.toString());
-                        }
-                    }
-                    mJsonWriter.endObject();
-                } else if (value instanceof Key[]) {
-                    Key[] keyboardKeys = (Key[]) value;
-                    mJsonWriter.beginArray();
-                    for (Key keyboardKey : keyboardKeys) {
-                        mJsonWriter.beginObject();
-                        mJsonWriter.name("code").value(keyboardKey.mCode);
-                        mJsonWriter.name("altCode").value(keyboardKey.getAltCode());
-                        mJsonWriter.name("x").value(keyboardKey.mX);
-                        mJsonWriter.name("y").value(keyboardKey.mY);
-                        mJsonWriter.name("w").value(keyboardKey.mWidth);
-                        mJsonWriter.name("h").value(keyboardKey.mHeight);
-                        mJsonWriter.endObject();
-                    }
-                    mJsonWriter.endArray();
-                } else if (value instanceof SuggestedWords) {
-                    SuggestedWords words = (SuggestedWords) value;
-                    mJsonWriter.beginObject();
-                    mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
-                    mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
-                    mJsonWriter.name("isPunctuationSuggestions")
-                            .value(words.mIsPunctuationSuggestions);
-                    mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
-                    mJsonWriter.name("isPrediction").value(words.mIsPrediction);
-                    mJsonWriter.name("words");
-                    mJsonWriter.beginArray();
-                    final int size = words.size();
-                    for (int j = 0; j < size; j++) {
-                        SuggestedWordInfo wordInfo = words.getWordInfo(j);
-                        mJsonWriter.value(wordInfo.toString());
-                    }
-                    mJsonWriter.endArray();
-                    mJsonWriter.endObject();
-                } else if (value == null) {
-                    mJsonWriter.nullValue();
-                } else {
-                    Log.w(TAG, "Unrecognized type to be logged: " +
-                            (value == null ? "<null>" : value.getClass().getName()));
-                    mJsonWriter.nullValue();
-                }
-            }
-            mJsonWriter.endObject();
         } catch (IOException e) {
             e.printStackTrace();
             Log.w(TAG, "Error in JsonWriter; disabling logging");
@@ -315,5 +221,6 @@
                 mJsonWriter = NULL_JSON_WRITER;
             }
         }
+        return mJsonWriter;
     }
 }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 38c3001..53f321f 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -83,7 +83,7 @@
  */
 public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = ResearchLogger.class.getSimpleName();
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
     private static final boolean LOG_EVERYTHING = false;  // true will disclose private info
     public static final boolean DEFAULT_USABILITY_STUDY_MODE = false;
     /* package */ static boolean sIsLogging = false;
@@ -94,8 +94,12 @@
     private static final String FILENAME_SUFFIX = ".txt";
     private static final SimpleDateFormat TIMESTAMP_DATEFORMAT =
             new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
+    // 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;
-    private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false;
+    // 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 || LOG_EVERYTHING;
     public static final int FEEDBACK_WORD_BUFFER_SIZE = 5;
 
     // constants related to specific log points
@@ -643,7 +647,8 @@
             final float savedStrokeWidth = paint.getStrokeWidth();
             if (IS_SHOWING_INDICATOR_CLEARLY) {
                 paint.setStrokeWidth(5);
-                canvas.drawRect(0, 0, width, height, paint);
+                canvas.drawLine(0, 0, 0, height, paint);
+                canvas.drawLine(width, 0, width, height, paint);
             } else {
                 // Put a tiny red dot on the screen so a knowledgeable user can check whether
                 // it is enabled.  The dot is actually a zero-width, zero-height rectangle,
@@ -681,7 +686,7 @@
         if (!mCurrentLogUnit.isEmpty()) {
             if (mMainLogBuffer != null) {
                 mMainLogBuffer.shiftIn(mCurrentLogUnit);
-                if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) {
+                if ((mMainLogBuffer.isSafeToLog() || LOG_EVERYTHING) && mMainResearchLog != null) {
                     publishLogBuffer(mMainLogBuffer, mMainResearchLog,
                             true /* isIncludingPrivateData */);
                     mMainLogBuffer.resetWordCounter();
@@ -801,10 +806,26 @@
         return WORD_REPLACEMENT_STRING;
     }
 
+    // Specific logging methods follow below.  The comments for each logging method should
+    // indicate what specific method is logged, and how to trigger it from the user interface.
+    //
+    // Logging methods can be generally classified into two flavors, "UserAction", which should
+    // correspond closely to an event that is sensed by the IME, and is usually generated
+    // directly by the user, and "SystemResponse" which corresponds to an event that the IME
+    // generates, often after much processing of user input.  SystemResponses should correspond
+    // closely to user-visible events.
+    // TODO: Consider exposing the UserAction classification in the log output.
+
+    /**
+     * Log a call to LatinIME.onStartInputViewInternal().
+     *
+     * UserAction: called each time the keyboard is opened up.
+     */
     private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL =
             new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid",
                     "packageName", "inputType", "imeOptions", "fieldId", "display", "model",
-                    "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything");
+                    "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything",
+                    "isExperimentalDebug");
     public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
             final SharedPreferences prefs) {
         final ResearchLogger researchLogger = getInstance();
@@ -825,7 +846,8 @@
                         Integer.toHexString(editorInfo.inputType),
                         Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
                         Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
-                        OUTPUT_FORMAT_VERSION, LOG_EVERYTHING);
+                        OUTPUT_FORMAT_VERSION, LOG_EVERYTHING,
+                        ProductionFlag.IS_EXPERIMENTAL_DEBUG);
             } catch (NameNotFoundException e) {
                 e.printStackTrace();
             }
@@ -836,6 +858,11 @@
         stop();
     }
 
+    /**
+     * Log a change in preferences.
+     *
+     * UserAction: called when the user changes the settings.
+     */
     private static final LogStatement LOGSTATEMENT_PREFS_CHANGED =
             new LogStatement("PrefsChanged", false, false, "prefs");
     public static void prefsChanged(final SharedPreferences prefs) {
@@ -843,8 +870,12 @@
         researchLogger.enqueueEvent(LOGSTATEMENT_PREFS_CHANGED, prefs);
     }
 
-    // Regular logging methods
-
+    /**
+     * Log a call to MainKeyboardView.processMotionEvent().
+     *
+     * UserAction: called when the user puts their finger onto the screen (ACTION_DOWN).
+     *
+     */
     private static final LogStatement LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT =
             new LogStatement("MainKeyboardViewProcessMotionEvent", true, false, "action",
                     "eventTime", "id", "x", "y", "size", "pressure");
@@ -869,6 +900,12 @@
         }
     }
 
+    /**
+     * Log a call to LatinIME.onCodeInput().
+     *
+     * SystemResponse: The main processing step for entering text.  Called when the user performs a
+     * tap, a flick, a long press, releases a gesture, or taps a punctuation suggestion.
+     */
     private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_CODE_INPUT =
             new LogStatement("LatinImeOnCodeInput", true, false, "code", "x", "y");
     public static void latinIME_onCodeInput(final int code, final int x, final int y) {
@@ -881,6 +918,12 @@
         }
         researchLogger.mStatistics.recordChar(code, time);
     }
+    /**
+     * Log a call to LatinIME.onDisplayCompletions().
+     *
+     * SystemResponse: The IME has displayed application-specific completions.  They may show up
+     * in the suggestion strip, such as a landscape phone.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_ONDISPLAYCOMPLETIONS =
             new LogStatement("LatinIMEOnDisplayCompletions", true, true,
                     "applicationSpecifiedCompletions");
@@ -898,6 +941,12 @@
         return returnValue;
     }
 
+    /**
+     * Log a call to LatinIME.onWindowHidden().
+     *
+     * UserAction: The user has performed an action that has caused the IME to be closed.  They may
+     * have focused on something other than a text field, or explicitly closed it.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_ONWINDOWHIDDEN =
             new LogStatement("LatinIMEOnWindowHidden", false, false, "isTextTruncated", "text");
     public static void latinIME_onWindowHidden(final int savedSelectionStart,
@@ -953,6 +1002,12 @@
         }
     }
 
+    /**
+     * Log a call to LatinIME.onUpdateSelection().
+     *
+     * UserAction/SystemResponse: The user has moved the cursor or selection.  This function may
+     * be called, however, when the system has moved the cursor, say by inserting a character.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_ONUPDATESELECTION =
             new LogStatement("LatinIMEOnUpdateSelection", true, false, "lastSelectionStart",
                     "lastSelectionEnd", "oldSelStart", "oldSelEnd", "newSelStart", "newSelEnd",
@@ -979,6 +1034,11 @@
                 expectingUpdateSelectionFromLogger, scrubbedWord);
     }
 
+    /**
+     * Log a call to LatinIME.pickSuggestionManually().
+     *
+     * UserAction: The user has chosen a specific word from the suggestion strip.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY =
             new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index",
                     "suggestion", "x", "y");
@@ -991,6 +1051,11 @@
                 Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
     }
 
+    /**
+     * Log a call to LatinIME.punctuationSuggestion().
+     *
+     * UserAction: The user has chosen punctuation from the punctuation suggestion strip.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION =
             new LogStatement("LatinIMEPunctuationSuggestion", false, false, "index", "suggestion",
                     "x", "y");
@@ -1000,6 +1065,13 @@
                 Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
     }
 
+    /**
+     * Log a call to LatinIME.sendKeyCodePoint().
+     *
+     * SystemResponse: The IME is simulating a hardware keypress.  This happens for numbers; other
+     * input typically goes through RichInputConnection.setComposingText() and
+     * RichInputConnection.commitText().
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT =
             new LogStatement("LatinIMESendKeyCodePoint", true, false, "code");
     public static void latinIME_sendKeyCodePoint(final int code) {
@@ -1011,18 +1083,36 @@
         }
     }
 
+    /**
+     * Log a call to LatinIME.swapSwapperAndSpace().
+     *
+     * SystemResponse: A symbol has been swapped with a space character.  E.g. punctuation may swap
+     * if a soft space is inserted after a word.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE =
             new LogStatement("LatinIMESwapSwapperAndSpace", false, false);
     public static void latinIME_swapSwapperAndSpace() {
         getInstance().enqueueEvent(LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE);
     }
 
+    /**
+     * Log a call to MainKeyboardView.onLongPress().
+     *
+     * UserAction: The user has performed a long-press on a key.
+     */
     private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS =
             new LogStatement("MainKeyboardViewOnLongPress", false, false);
     public static void mainKeyboardView_onLongPress() {
         getInstance().enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS);
     }
 
+    /**
+     * Log a call to MainKeyboardView.setKeyboard().
+     *
+     * SystemResponse: The IME has switched to a new keyboard (e.g. French, English).
+     * This is typically called right after LatinIME.onStartInputViewInternal (when starting a new
+     * IME), but may happen at other times if the user explicitly requests a keyboard change.
+     */
     private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD =
             new LogStatement("MainKeyboardViewSetKeyboard", false, false, "elementId", "locale",
                     "orientation", "width", "modeName", "action", "navigateNext",
@@ -1043,18 +1133,38 @@
                 keyboard.mOccupiedHeight, keyboard.mKeys);
     }
 
+    /**
+     * Log a call to LatinIME.revertCommit().
+     *
+     * SystemResponse: The IME has reverted commited text.  This happens when the user enters
+     * a word, commits it by pressing space or punctuation, and then reverts the commit by hitting
+     * backspace.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_REVERTCOMMIT =
             new LogStatement("LatinIMERevertCommit", true, false, "originallyTypedWord");
     public static void latinIME_revertCommit(final String originallyTypedWord) {
         getInstance().enqueueEvent(LOGSTATEMENT_LATINIME_REVERTCOMMIT, originallyTypedWord);
     }
 
+    /**
+     * Log a call to PointerTracker.callListenerOnCancelInput().
+     *
+     * UserAction: The user has canceled the input, e.g., by pressing down, but then removing
+     * outside the keyboard area.
+     * TODO: Verify
+     */
     private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT =
             new LogStatement("PointerTrackerCallListenerOnCancelInput", false, false);
     public static void pointerTracker_callListenerOnCancelInput() {
         getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT);
     }
 
+    /**
+     * Log a call to PointerTracker.callListenerOnCodeInput().
+     *
+     * SystemResponse: The user has entered a key through the normal tapping mechanism.
+     * LatinIME.onCodeInput will also be called.
+     */
     private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT =
             new LogStatement("PointerTrackerCallListenerOnCodeInput", true, false, "code",
                     "outputText", "x", "y", "ignoreModifierKey", "altersCode", "isEnabled");
@@ -1070,6 +1180,11 @@
         }
     }
 
+    /**
+     * Log a call to PointerTracker.callListenerCallListenerOnRelease().
+     *
+     * UserAction: The user has released their finger or thumb from the screen.
+     */
     private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE =
             new LogStatement("PointerTrackerCallListenerOnRelease", true, false, "code",
                     "withSliding", "ignoreModifierKey", "isEnabled");
@@ -1082,6 +1197,12 @@
         }
     }
 
+    /**
+     * Log a call to PointerTracker.onDownEvent().
+     *
+     * UserAction: The user has pressed down on a key.
+     * TODO: Differentiate with LatinIME.processMotionEvent.
+     */
     private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT =
             new LogStatement("PointerTrackerOnDownEvent", true, false, "deltaT", "distanceSquared");
     public static void pointerTracker_onDownEvent(long deltaT, int distanceSquared) {
@@ -1089,6 +1210,12 @@
                 distanceSquared);
     }
 
+    /**
+     * Log a call to PointerTracker.onMoveEvent().
+     *
+     * UserAction: The user has moved their finger while pressing on the screen.
+     * TODO: Differentiate with LatinIME.processMotionEvent().
+     */
     private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT =
             new LogStatement("PointerTrackerOnMoveEvent", true, false, "x", "y", "lastX", "lastY");
     public static void pointerTracker_onMoveEvent(final int x, final int y, final int lastX,
@@ -1096,6 +1223,12 @@
         getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT, x, y, lastX, lastY);
     }
 
+    /**
+     * Log a call to RichInputConnection.commitCompletion().
+     *
+     * SystemResponse: The IME has committed a completion.  A completion is an application-
+     * specific suggestion that is presented in a pop-up menu in the TextView.
+     */
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION =
             new LogStatement("RichInputConnectionCommitCompletion", true, false, "completionInfo");
     public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
@@ -1113,25 +1246,40 @@
         researchLogger.isExpectingCommitText = true;
     }
 
-    private static final LogStatement LOGSTATEMENT_COMMITTEXT_UPDATECURSOR =
-            new LogStatement("CommitTextUpdateCursor", true, false, "newCursorPosition");
+    /**
+     * Log a call to RichInputConnection.commitText().
+     *
+     * SystemResponse: The IME is committing text.  This happens after the user has typed a word
+     * and then a space or punctuation key.
+     */
+    private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT =
+            new LogStatement("RichInputConnectionCommitText", true, false, "newCursorPosition");
     public static void richInputConnection_commitText(final CharSequence committedWord,
             final int newCursorPosition) {
         final ResearchLogger researchLogger = getInstance();
         final String scrubbedWord = scrubDigitsFromString(committedWord.toString());
         if (!researchLogger.isExpectingCommitText) {
             researchLogger.onWordComplete(scrubbedWord, Long.MAX_VALUE, false /* isPartial */);
-            researchLogger.enqueueEvent(LOGSTATEMENT_COMMITTEXT_UPDATECURSOR, newCursorPosition);
+            researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT,
+                    newCursorPosition);
         }
         researchLogger.isExpectingCommitText = false;
     }
 
+    /**
+     * Shared event for logging committed text.
+     */
     private static final LogStatement LOGSTATEMENT_COMMITTEXT =
             new LogStatement("CommitText", true, false, "committedText");
     private void enqueueCommitText(final CharSequence word) {
         enqueueEvent(LOGSTATEMENT_COMMITTEXT, word);
     }
 
+    /**
+     * Log a call to RichInputConnection.deleteSurroundingText().
+     *
+     * SystemResponse: The IME has deleted text.
+     */
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT =
             new LogStatement("RichInputConnectionDeleteSurroundingText", true, false,
                     "beforeLength", "afterLength");
@@ -1141,20 +1289,37 @@
                 beforeLength, afterLength);
     }
 
+    /**
+     * Log a call to RichInputConnection.finishComposingText().
+     *
+     * SystemResponse: The IME has left the composing text as-is.
+     */
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT =
             new LogStatement("RichInputConnectionFinishComposingText", false, false);
     public static void richInputConnection_finishComposingText() {
         getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT);
     }
 
+    /**
+     * Log a call to RichInputConnection.performEditorAction().
+     *
+     * SystemResponse: The IME is invoking an action specific to the editor.
+     */
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION =
             new LogStatement("RichInputConnectionPerformEditorAction", false, false,
-                    "imeActionNext");
-    public static void richInputConnection_performEditorAction(final int imeActionNext) {
+                    "imeActionId");
+    public static void richInputConnection_performEditorAction(final int imeActionId) {
         getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION,
-                imeActionNext);
+                imeActionId);
     }
 
+    /**
+     * Log a call to RichInputConnection.sendKeyEvent().
+     *
+     * SystemResponse: The IME is telling the TextView that a key is being pressed through an
+     * alternate channel.
+     * TODO: only for hardware keys?
+     */
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT =
             new LogStatement("RichInputConnectionSendKeyEvent", true, false, "eventTime", "action",
                     "code");
@@ -1163,6 +1328,12 @@
                 keyEvent.getEventTime(), keyEvent.getAction(), keyEvent.getKeyCode());
     }
 
+    /**
+     * Log a call to RichInputConnection.setComposingText().
+     *
+     * SystemResponse: The IME is setting the composing text.  Happens each time a character is
+     * entered.
+     */
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT =
             new LogStatement("RichInputConnectionSetComposingText", true, true, "text",
                     "newCursorPosition");
@@ -1175,12 +1346,24 @@
                 newCursorPosition);
     }
 
+    /**
+     * Log a call to RichInputConnection.setSelection().
+     *
+     * SystemResponse: The IME is requesting that the selection change.  User-initiated selection-
+     * change requests do not go through this method -- it's only when the system wants to change
+     * the selection.
+     */
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION =
             new LogStatement("RichInputConnectionSetSelection", true, false, "from", "to");
     public static void richInputConnection_setSelection(final int from, final int to) {
         getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION, from, to);
     }
 
+    /**
+     * Log a call to SuddenJumpingTouchEventHandler.onTouchEvent().
+     *
+     * SystemResponse: The IME has filtered input events in case of an erroneous sensor reading.
+     */
     private static final LogStatement LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT =
             new LogStatement("SuddenJumpingTouchEventHandlerOnTouchEvent", true, false,
                     "motionEvent");
@@ -1191,6 +1374,11 @@
         }
     }
 
+    /**
+     * Log a call to SuggestionsView.setSuggestions().
+     *
+     * SystemResponse: The IME is setting the suggestions in the suggestion strip.
+     */
     private static final LogStatement LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS =
             new LogStatement("SuggestionStripViewSetSuggestions", true, true, "suggestedWords");
     public static void suggestionStripView_setSuggestions(final SuggestedWords suggestedWords) {
@@ -1200,12 +1388,22 @@
         }
     }
 
+    /**
+     * The user has indicated a particular point in the log that is of interest.
+     *
+     * UserAction: From direct menu invocation.
+     */
     private static final LogStatement LOGSTATEMENT_USER_TIMESTAMP =
             new LogStatement("UserTimestamp", false, false);
     public void userTimestamp() {
         getInstance().enqueueEvent(LOGSTATEMENT_USER_TIMESTAMP);
     }
 
+    /**
+     * Log a call to LatinIME.onEndBatchInput().
+     *
+     * SystemResponse: The system has completed a gesture.
+     */
     private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT =
             new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText",
                     "enteredWordPos");
@@ -1214,15 +1412,34 @@
         final ResearchLogger researchLogger = getInstance();
         researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
                 enteredWordPos);
-        researchLogger.mStatistics.recordGestureInput();
+        researchLogger.mStatistics.recordGestureInput(enteredText.length());
     }
 
+    /**
+     * Log a call to LatinIME.handleBackspace().
+     *
+     * UserInput: The user is deleting a gestured word by hitting the backspace key once.
+     */
+    private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH =
+            new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText");
+    public static void latinIME_handleBackspace_batch(final CharSequence deletedText) {
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText);
+        researchLogger.mStatistics.recordGestureDelete();
+    }
+
+    /**
+     * Log statistics.
+     *
+     * ContextualData, recorded at the end of a session.
+     */
     private static final LogStatement LOGSTATEMENT_STATISTICS =
             new LogStatement("Statistics", false, false, "charCount", "letterCount", "numberCount",
                     "spaceCount", "deleteOpsCount", "wordCount", "isEmptyUponStarting",
                     "isEmptinessStateKnown", "averageTimeBetweenKeys", "averageTimeBeforeDelete",
                     "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
-                    "dictionaryWordCount", "splitWordsCount", "gestureInputCount");
+                    "dictionaryWordCount", "splitWordsCount", "gestureInputCount",
+                    "gestureCharsCount", "gesturesDeletedCount");
     private static void logStatistics() {
         final ResearchLogger researchLogger = getInstance();
         final Statistics statistics = researchLogger.mStatistics;
@@ -1234,6 +1451,8 @@
                 statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
                 statistics.mAfterDeleteKeyCounter.getAverageTime(),
                 statistics.mDictionaryWordCount, statistics.mSplitWordsCount,
-                statistics.mGestureInputCount);
+                statistics.mGestureInputCount,
+                statistics.mGestureCharsCount,
+                statistics.mGesturesDeletedCount);
     }
 }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index 90d7f38..f9c0729 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -19,10 +19,11 @@
 import android.util.Log;
 
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.define.ProductionFlag;
 
 public class Statistics {
     private static final String TAG = Statistics.class.getSimpleName();
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
 
     // Number of characters entered during a typing session
     int mCharCount;
@@ -42,6 +43,10 @@
     int mSplitWordsCount;
     // Number of gestures that were input.
     int mGestureInputCount;
+    // Number of gestures that were deleted.
+    int mGesturesDeletedCount;
+    // Total number of characters in words entered by gesture.
+    int mGestureCharsCount;
     // Whether the text field was empty upon editing
     boolean mIsEmptyUponStarting;
     boolean mIsEmptinessStateKnown;
@@ -108,6 +113,8 @@
         mBeforeDeleteKeyCounter.reset();
         mDuringRepeatedDeleteKeysCounter.reset();
         mAfterDeleteKeyCounter.reset();
+        mGestureCharsCount = 0;
+        mGesturesDeletedCount = 0;
 
         mLastTapTime = 0;
         mIsLastKeyDeleteKey = false;
@@ -160,12 +167,17 @@
         mSplitWordsCount++;
     }
 
-    public void recordGestureInput() {
+    public void recordGestureInput(final int numCharsEntered) {
         mGestureInputCount++;
+        mGestureCharsCount += numCharsEntered;
     }
 
     public void setIsEmptyUponStarting(final boolean isEmpty) {
         mIsEmptyUponStarting = isEmpty;
         mIsEmptinessStateKnown = true;
     }
+
+    public void recordGestureDelete() {
+        mGesturesDeletedCount++;
+    }
 }
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index c616be5..a8a8871 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -26,7 +26,7 @@
 LATIN_IME_SRC_DIR := src
 LATIN_IME_SRC_FULLPATH_DIR := $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
 
-LOCAL_C_INCLUDES += $(LATIN_IME_SRC_FULLPATH_DIR) $(LATIN_IME_SRC_FULLPATH_DIR)/gesture
+LOCAL_C_INCLUDES += $(LATIN_IME_SRC_FULLPATH_DIR) $(LATIN_IME_SRC_FULLPATH_DIR)/suggest
 
 LOCAL_CFLAGS += -Werror -Wall -Wextra -Weffc++ -Wformat=2 -Wcast-qual -Wcast-align \
     -Wwrite-strings -Wfloat-equal -Wpointer-arith -Winit-self -Wredundant-decls -Wno-system-headers
@@ -57,8 +57,8 @@
     proximity_info_state.cpp \
     unigram_dictionary.cpp \
     words_priority_queue.cpp \
-    gesture/gesture_decoder_wrapper.cpp \
-    gesture/incremental_decoder_wrapper.cpp
+    suggest/gesture_suggest.cpp \
+    suggest/typing_suggest.cpp
 
 LOCAL_SRC_FILES := \
     $(LATIN_IME_JNI_SRC_FILES) \
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index 8210aa0..167b36f 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -23,7 +23,7 @@
 #include "defines.h"
 #include "dictionary.h"
 #include "dic_traverse_wrapper.h"
-#include "gesture_decoder_wrapper.h"
+#include "gesture_suggest.h"
 #include "unigram_dictionary.h"
 
 namespace latinime {
@@ -36,7 +36,7 @@
           mUnigramDictionary(new UnigramDictionary(mOffsetDict, maxWordLength, maxWords,
                   BinaryFormat::getFlags(mDict))),
           mBigramDictionary(new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions)),
-          mGestureDecoder(new GestureDecoderWrapper(maxWordLength, maxWords)) {
+          mGestureSuggest(new GestureSuggest(maxWordLength, maxWords)) {
     if (DEBUG_DICT) {
         if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) {
             AKLOGI("Max word length (%d) is greater than %d",
@@ -49,7 +49,7 @@
 Dictionary::~Dictionary() {
     delete mUnigramDictionary;
     delete mBigramDictionary;
-    delete mGestureDecoder;
+    delete mGestureSuggest;
 }
 
 int Dictionary::getSuggestions(ProximityInfo *proximityInfo, void *traverseSession,
@@ -61,7 +61,7 @@
     if (isGesture) {
         DicTraverseWrapper::initDicTraverseSession(
                 traverseSession, this, prevWordChars, prevWordLength);
-        result = mGestureDecoder->getSuggestions(proximityInfo, traverseSession,
+        result = mGestureSuggest->getSuggestions(proximityInfo, traverseSession,
                 xcoordinates, ycoordinates, times, pointerIds, codes, codesSize, commitPoint,
                 outWords, frequencies, spaceIndices, outputTypes);
         if (DEBUG_DICT) {
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index e966000..26edc4f 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -24,8 +24,8 @@
 namespace latinime {
 
 class BigramDictionary;
-class IncrementalDecoderInterface;
 class ProximityInfo;
+class SuggestInterface;
 class UnigramDictionary;
 
 class Dictionary {
@@ -83,7 +83,7 @@
 
     const UnigramDictionary *mUnigramDictionary;
     const BigramDictionary *mBigramDictionary;
-    IncrementalDecoderInterface *mGestureDecoder;
+    SuggestInterface *mGestureSuggest;
 };
 
 // public static utility methods
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h
deleted file mode 100644
index b968149..0000000
--- a/native/jni/src/gesture/gesture_decoder_wrapper.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#ifndef LATINIME_GESTURE_DECODER_WRAPPER_H
-#define LATINIME_GESTURE_DECODER_WRAPPER_H
-
-#include "defines.h"
-#include "incremental_decoder_interface.h"
-
-namespace latinime {
-
-class UnigramDictionary;
-class BigramDictionary;
-class ProximityInfo;
-
-class GestureDecoderWrapper : public IncrementalDecoderInterface {
- public:
-    GestureDecoderWrapper(const int maxWordLength, const int maxWords)
-            : mIncrementalDecoderInterface(getGestureDecoderInstance(maxWordLength, maxWords)) {
-    }
-
-    virtual ~GestureDecoderWrapper();
-
-    int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
-            int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, int *outWords,
-            int *frequencies, int *outputIndices, int *outputTypes) const {
-        if (!mIncrementalDecoderInterface) {
-            return 0;
-        }
-        return mIncrementalDecoderInterface->getSuggestions(pInfo, traverseSession, inputXs,
-                inputYs, times, pointerIds, codes, inputSize, commitPoint, outWords, frequencies,
-                outputIndices, outputTypes);
-    }
-
-    static void setGestureDecoderFactoryMethod(
-            IncrementalDecoderInterface *(*factoryMethod)(int, int)) {
-        sGestureDecoderFactoryMethod = factoryMethod;
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(GestureDecoderWrapper);
-    static IncrementalDecoderInterface *getGestureDecoderInstance(int maxWordLength, int maxWords) {
-        if (sGestureDecoderFactoryMethod) {
-            return sGestureDecoderFactoryMethod(maxWordLength, maxWords);
-        }
-        return 0;
-    }
-
-    static IncrementalDecoderInterface *(*sGestureDecoderFactoryMethod)(int, int);
-    IncrementalDecoderInterface *mIncrementalDecoderInterface;
-};
-} // namespace latinime
-#endif // LATINIME_GESTURE_DECODER_WRAPPER_H
diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.cpp b/native/jni/src/gesture/incremental_decoder_wrapper.cpp
deleted file mode 100644
index f6e4562..0000000
--- a/native/jni/src/gesture/incremental_decoder_wrapper.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "incremental_decoder_wrapper.h"
-
-namespace latinime {
-    IncrementalDecoderInterface *
-            (*IncrementalDecoderWrapper::sIncrementalDecoderFactoryMethod)(int, int) = 0;
-
-    IncrementalDecoderWrapper::~IncrementalDecoderWrapper() {
-        delete mIncrementalDecoderInterface;
-    }
-} // namespace latinime
diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.h b/native/jni/src/gesture/incremental_decoder_wrapper.h
deleted file mode 100644
index c15b439..0000000
--- a/native/jni/src/gesture/incremental_decoder_wrapper.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#ifndef LATINIME_INCREMENTAL_DECODER_WRAPPER_H
-#define LATINIME_INCREMENTAL_DECODER_WRAPPER_H
-
-#include "defines.h"
-#include "incremental_decoder_interface.h"
-
-namespace latinime {
-
-class UnigramDictionary;
-class BigramDictionary;
-class ProximityInfo;
-
-class IncrementalDecoderWrapper : public IncrementalDecoderInterface {
- public:
-    IncrementalDecoderWrapper(const int maxWordLength, const int maxWords)
-            : mIncrementalDecoderInterface(getIncrementalDecoderInstance(maxWordLength, maxWords)) {
-    }
-
-    virtual ~IncrementalDecoderWrapper();
-
-    int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
-            int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, int *outWords,
-            int *frequencies, int *outputIndices, int *outputTypes) const {
-        if (!mIncrementalDecoderInterface) {
-            return 0;
-        }
-        return mIncrementalDecoderInterface->getSuggestions(pInfo, traverseSession, inputXs,
-                inputYs, times, pointerIds, codes, inputSize, commitPoint, outWords, frequencies,
-                outputIndices, outputTypes);
-    }
-
-    static void setIncrementalDecoderFactoryMethod(
-            IncrementalDecoderInterface *(*factoryMethod)(int, int)) {
-        sIncrementalDecoderFactoryMethod = factoryMethod;
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(IncrementalDecoderWrapper);
-    static IncrementalDecoderInterface *getIncrementalDecoderInstance(int maxWordLength,
-            int maxWords) {
-        if (sIncrementalDecoderFactoryMethod) {
-            return sIncrementalDecoderFactoryMethod(maxWordLength, maxWords);
-        }
-        return 0;
-    }
-
-    static IncrementalDecoderInterface *(*sIncrementalDecoderFactoryMethod)(int, int);
-    IncrementalDecoderInterface *mIncrementalDecoderInterface;
-};
-} // namespace latinime
-#endif // LATINIME_INCREMENTAL_DECODER_WRAPPER_H
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.cpp b/native/jni/src/suggest/gesture_suggest.cpp
similarity index 72%
rename from native/jni/src/gesture/gesture_decoder_wrapper.cpp
rename to native/jni/src/suggest/gesture_suggest.cpp
index 20ad4a5..2a604b8 100644
--- a/native/jni/src/gesture/gesture_decoder_wrapper.cpp
+++ b/native/jni/src/suggest/gesture_suggest.cpp
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#include "gesture_decoder_wrapper.h"
+#include "gesture_suggest.h"
 
 namespace latinime {
-    IncrementalDecoderInterface *
-            (*GestureDecoderWrapper::sGestureDecoderFactoryMethod)(int, int) = 0;
+    SuggestInterface *(*GestureSuggest::sGestureSuggestFactoryMethod)(int, int) = 0;
 
-    GestureDecoderWrapper::~GestureDecoderWrapper() {
-        delete mIncrementalDecoderInterface;
+    GestureSuggest::~GestureSuggest() {
+        delete mSuggestInterface;
     }
 } // namespace latinime
diff --git a/native/jni/src/suggest/gesture_suggest.h b/native/jni/src/suggest/gesture_suggest.h
new file mode 100644
index 0000000..e4af03f
--- /dev/null
+++ b/native/jni/src/suggest/gesture_suggest.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef LATINIME_GESTURE_SUGGEST_H
+#define LATINIME_GESTURE_SUGGEST_H
+
+#include "defines.h"
+#include "suggest_interface.h"
+
+namespace latinime {
+
+class ProximityInfo;
+
+class GestureSuggest : public SuggestInterface {
+ public:
+    GestureSuggest(const int maxWordLength, const int maxWords)
+            : mSuggestInterface(getGestureSuggestInstance(maxWordLength, maxWords)) {
+    }
+
+    virtual ~GestureSuggest();
+
+    int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
+            int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, int *outWords,
+            int *frequencies, int *outputIndices, int *outputTypes) const {
+        if (!mSuggestInterface) {
+            return 0;
+        }
+        return mSuggestInterface->getSuggestions(pInfo, traverseSession, inputXs, inputYs, times,
+                pointerIds, codes, inputSize, commitPoint, outWords, frequencies, outputIndices,
+                outputTypes);
+    }
+
+    static void setGestureSuggestFactoryMethod(SuggestInterface *(*factoryMethod)(int, int)) {
+        sGestureSuggestFactoryMethod = factoryMethod;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(GestureSuggest);
+    static SuggestInterface *getGestureSuggestInstance(int maxWordLength, int maxWords) {
+        if (!sGestureSuggestFactoryMethod) {
+            return 0;
+        }
+        return sGestureSuggestFactoryMethod(maxWordLength, maxWords);
+    }
+
+    static SuggestInterface *(*sGestureSuggestFactoryMethod)(int, int);
+    SuggestInterface *mSuggestInterface;
+};
+} // namespace latinime
+#endif // LATINIME_GESTURE_SUGGEST_H
diff --git a/native/jni/src/gesture/incremental_decoder_interface.h b/native/jni/src/suggest/suggest_interface.h
similarity index 72%
rename from native/jni/src/gesture/incremental_decoder_interface.h
rename to native/jni/src/suggest/suggest_interface.h
index ff85adc..de58e79 100644
--- a/native/jni/src/gesture/incremental_decoder_interface.h
+++ b/native/jni/src/suggest/suggest_interface.h
@@ -14,26 +14,24 @@
  * limitations under the License.
  */
 
-#ifndef LATINIME_INCREMENTAL_DECODER_INTERFACE_H
-#define LATINIME_INCREMENTAL_DECODER_INTERFACE_H
+#ifndef LATINIME_SUGGEST_INTERFACE_H
+#define LATINIME_SUGGEST_INTERFACE_H
 
 #include "defines.h"
 
 namespace latinime {
 
-class UnigramDictionary;
-class BigramDictionary;
 class ProximityInfo;
 
-class IncrementalDecoderInterface {
+class SuggestInterface {
  public:
     virtual int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs,
             int *inputYs, int *times, int *pointerIds, int *codes, int inputSize, int commitPoint,
             int *outWords, int *frequencies, int *outputIndices, int *outputTypes) const = 0;
-    IncrementalDecoderInterface() { };
-    virtual ~IncrementalDecoderInterface() { };
+    SuggestInterface() {};
+    virtual ~SuggestInterface() {};
  private:
-    DISALLOW_COPY_AND_ASSIGN(IncrementalDecoderInterface);
+    DISALLOW_COPY_AND_ASSIGN(SuggestInterface);
 };
 } // namespace latinime
-#endif // LATINIME_INCREMENTAL_DECODER_INTERFACE_H
+#endif // LATINIME_SUGGEST_INTERFACE_H
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.cpp b/native/jni/src/suggest/typing_suggest.cpp
similarity index 72%
copy from native/jni/src/gesture/gesture_decoder_wrapper.cpp
copy to native/jni/src/suggest/typing_suggest.cpp
index 20ad4a5..40d4a98 100644
--- a/native/jni/src/gesture/gesture_decoder_wrapper.cpp
+++ b/native/jni/src/suggest/typing_suggest.cpp
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#include "gesture_decoder_wrapper.h"
+#include "typing_suggest.h"
 
 namespace latinime {
-    IncrementalDecoderInterface *
-            (*GestureDecoderWrapper::sGestureDecoderFactoryMethod)(int, int) = 0;
+    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)(int, int) = 0;
 
-    GestureDecoderWrapper::~GestureDecoderWrapper() {
-        delete mIncrementalDecoderInterface;
+    TypingSuggest::~TypingSuggest() {
+        delete mSuggestInterface;
     }
 } // namespace latinime
diff --git a/native/jni/src/suggest/typing_suggest.h b/native/jni/src/suggest/typing_suggest.h
new file mode 100644
index 0000000..9de4158
--- /dev/null
+++ b/native/jni/src/suggest/typing_suggest.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef LATINIME_TYPING_SUGGEST_H
+#define LATINIME_TYPING_SUGGEST_H
+
+#include "defines.h"
+#include "suggest_interface.h"
+
+namespace latinime {
+
+class ProximityInfo;
+
+class TypingSuggest : public SuggestInterface {
+ public:
+    TypingSuggest(const int maxWordLength, const int maxWords)
+            : mSuggestInterface(getTypingSuggestInstance(maxWordLength, maxWords)) {
+    }
+
+    virtual ~TypingSuggest();
+
+    int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
+            int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, int *outWords,
+            int *frequencies, int *outputIndices, int *outputTypes) const {
+        if (!mSuggestInterface) {
+            return 0;
+        }
+        return mSuggestInterface->getSuggestions(pInfo, traverseSession, inputXs, inputYs, times,
+                pointerIds, codes, inputSize, commitPoint, outWords, frequencies, outputIndices,
+                outputTypes);
+    }
+
+    static void setTypingSuggestFactoryMethod(SuggestInterface *(*factoryMethod)(int, int)) {
+        sTypingSuggestFactoryMethod = factoryMethod;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TypingSuggest);
+    static SuggestInterface *getTypingSuggestInstance(int maxWordLength, int maxWords) {
+        if (!sTypingSuggestFactoryMethod) {
+            return 0;
+        }
+        return sTypingSuggestFactoryMethod(maxWordLength, maxWords);
+    }
+
+    static SuggestInterface *(*sTypingSuggestFactoryMethod)(int, int);
+    SuggestInterface *mSuggestInterface;
+};
+} // namespace latinime
+#endif // LATINIME_TYPING_SUGGEST_H