Merge "[Rlog36z] Pass LatinIME instance to logger as LatinIME class"
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/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
index ae7b157..a3c3e89 100644
--- a/java/src/com/android/inputmethod/research/LogBuffer.java
+++ b/java/src/com/android/inputmethod/research/LogBuffer.java
@@ -110,4 +110,8 @@
         }
         return logUnit;
     }
+
+    public boolean isEmpty() {
+        return mLogUnits.isEmpty();
+    }
 }
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index 884ade0..27c4027 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -24,10 +24,12 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger.LogStatement;
 
 import java.io.IOException;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -95,6 +97,22 @@
      */
     public synchronized void publishTo(final ResearchLog researchLog,
             final boolean isIncludingPrivateData) {
+        // Prepare debugging output if necessary
+        final StringWriter debugStringWriter;
+        final JsonWriter debugJsonWriter;
+        if (DEBUG) {
+            debugStringWriter = new StringWriter();
+            debugJsonWriter = new JsonWriter(debugStringWriter);
+            debugJsonWriter.setIndent("  ");
+            try {
+                debugJsonWriter.beginArray();
+            } catch (IOException e) {
+                Log.e(TAG, "Could not open array in JsonWriter", e);
+            }
+        } else {
+            debugStringWriter = null;
+            debugJsonWriter = null;
+        }
         final int size = mLogStatementList.size();
         // Write out any logStatement that passes the privacy filter.
         for (int i = 0; i < size; i++) {
@@ -110,6 +128,23 @@
             final JsonWriter jsonWriter = researchLog.getValidJsonWriterLocked();
             outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i),
                     mTimeList.get(i));
+            if (DEBUG) {
+                outputLogStatementToLocked(debugJsonWriter, mLogStatementList.get(i),
+                        mValuesList.get(i), mTimeList.get(i));
+            }
+        }
+        if (DEBUG) {
+            try {
+                debugJsonWriter.endArray();
+                debugJsonWriter.flush();
+            } catch (IOException e) {
+                Log.e(TAG, "Could not close array in JsonWriter", e);
+            }
+            final String bigString = debugStringWriter.getBuffer().toString();
+            final String[] lines = bigString.split("\n");
+            for (String line : lines) {
+                Log.d(TAG, line);
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 4b95cde..48ce832 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -704,6 +704,7 @@
     /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
             final ResearchLog researchLog, final boolean isIncludingPrivateData) {
         final LogUnit openingLogUnit = new LogUnit();
+        if (logBuffer.isEmpty()) return;
         openingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_OPENING, SystemClock.uptimeMillis(),
                 isIncludingPrivateData);
         researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */);
@@ -804,6 +805,21 @@
         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",
@@ -842,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) {
@@ -849,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");
@@ -875,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) {
@@ -887,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");
@@ -904,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,
@@ -959,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",
@@ -985,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");
@@ -997,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");
@@ -1006,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) {
@@ -1017,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",
@@ -1049,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");
@@ -1076,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");
@@ -1088,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) {
@@ -1095,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,
@@ -1102,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) {
@@ -1119,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");
@@ -1147,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");
@@ -1169,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");
@@ -1181,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");
@@ -1197,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) {
@@ -1206,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");
@@ -1220,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;
@@ -1240,6 +1451,8 @@
                 statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
                 statistics.mAfterDeleteKeyCounter.getAverageTime(),
                 statistics.mDictionaryWordCount, statistics.mSplitWordsCount,
-                statistics.mGestureInputCount);
+                statistics.mGesturesInputCount,
+                statistics.mGesturesCharsCount,
+                statistics.mGesturesDeletedCount);
     }
 }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index 23d1050..a02aabf 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -42,7 +42,11 @@
     // Number of words split and spaces automatically entered.
     int mSplitWordsCount;
     // Number of gestures that were input.
-    int mGestureInputCount;
+    int mGesturesInputCount;
+    // Number of gestures that were deleted.
+    int mGesturesDeletedCount;
+    // Total number of characters in words entered by gesture.
+    int mGesturesCharsCount;
     // Whether the text field was empty upon editing
     boolean mIsEmptyUponStarting;
     boolean mIsEmptinessStateKnown;
@@ -103,12 +107,18 @@
         mSpaceCount = 0;
         mDeleteKeyCount = 0;
         mWordCount = 0;
+        mDictionaryWordCount = 0;
+        mSplitWordsCount = 0;
+        mGesturesInputCount = 0;
+        mGesturesDeletedCount = 0;
         mIsEmptyUponStarting = true;
         mIsEmptinessStateKnown = false;
         mKeyCounter.reset();
         mBeforeDeleteKeyCounter.reset();
         mDuringRepeatedDeleteKeysCounter.reset();
         mAfterDeleteKeyCounter.reset();
+        mGesturesCharsCount = 0;
+        mGesturesDeletedCount = 0;
 
         mLastTapTime = 0;
         mIsLastKeyDeleteKey = false;
@@ -161,12 +171,17 @@
         mSplitWordsCount++;
     }
 
-    public void recordGestureInput() {
-        mGestureInputCount++;
+    public void recordGestureInput(final int numCharsEntered) {
+        mGesturesInputCount++;
+        mGesturesCharsCount += numCharsEntered;
     }
 
     public void setIsEmptyUponStarting(final boolean isEmpty) {
         mIsEmptyUponStarting = isEmpty;
         mIsEmptinessStateKnown = true;
     }
+
+    public void recordGestureDelete() {
+        mGesturesDeletedCount++;
+    }
 }
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index 26a0fab..a1ecc11 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -46,9 +46,10 @@
 
 public final class UploaderService extends IntentService {
     private static final String TAG = UploaderService.class.getSimpleName();
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
     // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing
-    private static final boolean IS_INHIBITING_AUTO_UPLOAD =
-            false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;  // Force false in production
+    private static final boolean IS_INHIBITING_AUTO_UPLOAD = false
+            && ProductionFlag.IS_EXPERIMENTAL_DEBUG;  // Force false in production
     public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
     private static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
             + ".extra.UPLOAD_UNCONDITIONALLY";
@@ -146,7 +147,9 @@
     }
 
     private boolean uploadFile(File file) {
-        Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+        if (DEBUG) {
+            Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+        }
         boolean success = false;
         final int contentLength = (int) file.length();
         HttpURLConnection connection = null;
@@ -162,6 +165,9 @@
             int numBytesRead;
             while ((numBytesRead = fileInputStream.read(buf)) != -1) {
                 os.write(buf, 0, numBytesRead);
+                if (DEBUG) {
+                    Log.d(TAG, new String(buf));
+                }
             }
             if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                 Log.d(TAG, "upload failed: " + connection.getResponseCode());
@@ -176,7 +182,9 @@
             }
             file.delete();
             success = true;
-            Log.d(TAG, "upload successful");
+            if (DEBUG) {
+                Log.d(TAG, "upload successful");
+            }
         } catch (Exception e) {
             e.printStackTrace();
         } finally {