am 2fb40ef3: am 15b28432: Merge "Cleanup unused code" into jb-mr1-dev

* commit '2fb40ef30121da62386d64a2c9c5f5abffdcbc98':
  Cleanup unused code
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f8a6fc8..f361439 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -891,13 +891,13 @@
                 }
             }
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
-        }
         if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
         mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
         if (applicationSpecifiedCompletions == null) {
             clearSuggestionStrip();
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_onDisplayCompletions(null);
+            }
             return;
         }
 
@@ -919,6 +919,9 @@
         // this case? This says to keep whatever the user typed.
         mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
         setSuggestionStripShown(true);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
+        }
     }
 
     private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
@@ -1038,9 +1041,6 @@
         final CharSequence typedWord = mWordComposer.getTypedWord();
         if (typedWord.length() > 0) {
             mConnection.commitText(typedWord, 1);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_commitText(typedWord);
-            }
             final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
             mLastComposedWord = mWordComposer.commitWord(
                     LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
@@ -1083,12 +1083,9 @@
         if (lastTwo != null && lastTwo.length() == 2
                 && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
             mConnection.deleteSurroundingText(2, 0);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_deleteSurroundingText(2);
-            }
             mConnection.commitText(lastTwo.charAt(1) + " ", 1);
             if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
+                ResearchLogger.latinIME_swapSwapperAndSpace();
             }
             mKeyboardSwitcher.updateShiftState();
         }
@@ -1105,9 +1102,6 @@
             mHandler.cancelDoubleSpacesTimer();
             mConnection.deleteSurroundingText(2, 0);
             mConnection.commitText(". ", 1);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_doubleSpaceAutoPeriod();
-            }
             mKeyboardSwitcher.updateShiftState();
             return true;
         }
@@ -1171,9 +1165,6 @@
 
     private void performEditorAction(int actionId) {
         mConnection.performEditorAction(actionId);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_performEditorAction(actionId);
-        }
     }
 
     private void handleLanguageSwitchKey() {
@@ -1210,6 +1201,9 @@
         // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
         if (code >= '0' && code <= '9') {
             super.sendKeyChar((char)code);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_sendKeyCodePoint(code);
+            }
             return;
         }
 
@@ -1226,9 +1220,6 @@
             final String text = new String(new int[] { code }, 0, 1);
             mConnection.commitText(text, text.length());
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_sendKeyCodePoint(code);
-        }
     }
 
     // Implementation of {@link KeyboardActionListener}.
@@ -1338,9 +1329,6 @@
             sendKeyCodePoint(Keyboard.CODE_SPACE);
         }
         mConnection.commitText(text, 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_commitText(text);
-        }
         mConnection.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
@@ -1435,9 +1423,6 @@
             // like the smiley key or the .com key.
             final int length = mEnteredText.length();
             mConnection.deleteSurroundingText(length, 0);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_deleteSurroundingText(length);
-            }
             // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
             // In addition we know that spaceState is false, and that we should not be
             // reverting any autocorrect at this point. So we can safely return.
@@ -1457,9 +1442,6 @@
                 mHandler.postUpdateSuggestionStrip();
             } else {
                 mConnection.deleteSurroundingText(1, 0);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_deleteSurroundingText(1);
-                }
             }
         } else {
             if (mLastComposedWord.canRevertCommit()) {
@@ -1488,9 +1470,6 @@
                 final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
                 mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
                 mConnection.deleteSurroundingText(lengthToDelete, 0);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
-                }
             } else {
                 // There is no selection, just delete one character.
                 if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -1509,14 +1488,8 @@
                 } else {
                     mConnection.deleteSurroundingText(1, 0);
                 }
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_deleteSurroundingText(1);
-                }
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
                     mConnection.deleteSurroundingText(1, 0);
-                    if (ProductionFlag.IS_EXPERIMENTAL) {
-                        ResearchLogger.latinIME_deleteSurroundingText(1);
-                    }
                 }
             }
             if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
@@ -1840,10 +1813,6 @@
                         + "is empty? Impossible! I must commit suicide.");
             }
             Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
-                        autoCorrection.toString());
-            }
             mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
                     separatorCodePoint);
@@ -1869,13 +1838,13 @@
             // So, LatinImeLogger logs "" as a user's input.
             LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
             // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
-            }
             final int primaryCode = suggestion.charAt(0);
             onCodeInput(primaryCode,
                     KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
                     KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
+            }
             return;
         }
 
@@ -1902,10 +1871,6 @@
             final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
             mConnection.commitCompletion(completionInfo);
             mConnection.endBatchEdit();
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
-                        completionInfo.getText(), x, y);
-            }
             return;
         }
 
@@ -1914,12 +1879,12 @@
         final String replacedWord = mWordComposer.getTypedWord().toString();
         LatinImeLogger.logOnManualSuggestion(replacedWord,
                 suggestion.toString(), index, suggestedWords);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
-        }
         mExpectingUpdateSelection = true;
         commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
                 LastComposedWord.NOT_A_SEPARATOR);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
+        }
         mConnection.endBatchEdit();
         // Don't allow cancellation of manual pick
         mLastComposedWord.deactivate();
@@ -1954,9 +1919,6 @@
         final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
         mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
                 this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_commitText(chosenWord);
-        }
         // Add the word to the user history dictionary
         final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
         // TODO: figure out here if this is an auto-correct or if the best word is actually
@@ -2023,9 +1985,6 @@
         mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
         final int length = word.length();
         mConnection.deleteSurroundingText(length, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(length);
-        }
         mConnection.setComposingText(word, 1);
         mHandler.postUpdateSuggestionStrip();
     }
@@ -2053,9 +2012,6 @@
             }
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
-        }
         if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
             mUserHistoryDictionary.cancelAddingUserHistory(
                     previousWord.toString(), committedWord.toString());
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8b4c173..41e59e9 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -55,7 +55,9 @@
     public void beginBatchEdit() {
         if (++mNestLevel == 1) {
             mIC = mParent.getCurrentInputConnection();
-            if (null != mIC) mIC.beginBatchEdit();
+            if (null != mIC) {
+                mIC.beginBatchEdit();
+            }
         } else {
             if (DBG) {
                 throw new RuntimeException("Nest level too deep");
@@ -66,7 +68,9 @@
     }
     public void endBatchEdit() {
         if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
-        if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
+        if (--mNestLevel == 0 && null != mIC) {
+            mIC.endBatchEdit();
+        }
     }
 
     private void checkBatchEdit() {
@@ -79,12 +83,22 @@
 
     public void finishComposingText() {
         checkBatchEdit();
-        if (null != mIC) mIC.finishComposingText();
+        if (null != mIC) {
+            mIC.finishComposingText();
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_finishComposingText();
+            }
+        }
     }
 
     public void commitText(final CharSequence text, final int i) {
         checkBatchEdit();
-        if (null != mIC) mIC.commitText(text, i);
+        if (null != mIC) {
+            mIC.commitText(text, i);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_commitText(text, i);
+            }
+        }
     }
 
     public int getCursorCapsMode(final int inputType) {
@@ -107,37 +121,72 @@
 
     public void deleteSurroundingText(final int i, final int j) {
         checkBatchEdit();
-        if (null != mIC) mIC.deleteSurroundingText(i, j);
+        if (null != mIC) {
+            mIC.deleteSurroundingText(i, j);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+            }
+        }
     }
 
     public void performEditorAction(final int actionId) {
         mIC = mParent.getCurrentInputConnection();
-        if (null != mIC) mIC.performEditorAction(actionId);
+        if (null != mIC) {
+            mIC.performEditorAction(actionId);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_performEditorAction(actionId);
+            }
+        }
     }
 
     public void sendKeyEvent(final KeyEvent keyEvent) {
         checkBatchEdit();
-        if (null != mIC) mIC.sendKeyEvent(keyEvent);
+        if (null != mIC) {
+            mIC.sendKeyEvent(keyEvent);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
+            }
+        }
     }
 
     public void setComposingText(final CharSequence text, final int i) {
         checkBatchEdit();
-        if (null != mIC) mIC.setComposingText(text, i);
+        if (null != mIC) {
+            mIC.setComposingText(text, i);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_setComposingText(text, i);
+            }
+        }
     }
 
     public void setSelection(final int from, final int to) {
         checkBatchEdit();
-        if (null != mIC) mIC.setSelection(from, to);
+        if (null != mIC) {
+            mIC.setSelection(from, to);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_setSelection(from, to);
+            }
+        }
     }
 
     public void commitCorrection(final CorrectionInfo correctionInfo) {
         checkBatchEdit();
-        if (null != mIC) mIC.commitCorrection(correctionInfo);
+        if (null != mIC) {
+            mIC.commitCorrection(correctionInfo);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
+            }
+        }
     }
 
     public void commitCompletion(final CompletionInfo completionInfo) {
         checkBatchEdit();
-        if (null != mIC) mIC.commitCompletion(completionInfo);
+        if (null != mIC) {
+            mIC.commitCompletion(completionInfo);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.richInputConnection_commitCompletion(completionInfo);
+            }
+        }
     }
 
     public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
@@ -315,9 +364,6 @@
         if (lastOne != null && lastOne.length() == 1
                 && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
             deleteSurroundingText(1, 0);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_deleteSurroundingText(1);
-            }
         }
     }
 
@@ -382,13 +428,7 @@
             return false;
         }
         deleteSurroundingText(2, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(2);
-        }
         commitText("  ", 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
-        }
         return true;
     }
 
@@ -409,13 +449,7 @@
             return false;
         }
         deleteSurroundingText(2, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(2);
-        }
         commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_revertSwapPunctuation();
-        }
         return true;
     }
 }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 68bd98a..07b8c59 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -34,15 +34,18 @@
 import android.inputmethodservice.InputMethodService;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.SystemClock;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.Button;
@@ -135,10 +138,12 @@
     private Dictionary mDictionary;
     private KeyboardSwitcher mKeyboardSwitcher;
     private InputMethodService mInputMethodService;
+    private final Statistics mStatistics;
 
     private ResearchLogUploader mResearchLogUploader;
 
     private ResearchLogger() {
+        mStatistics = Statistics.getInstance();
     }
 
     public static ResearchLogger getInstance() {
@@ -268,10 +273,35 @@
         return new File(filesDir, sb.toString());
     }
 
+    private void checkForEmptyEditor() {
+        if (mInputMethodService == null) {
+            return;
+        }
+        final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+        if (ic == null) {
+            return;
+        }
+        final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
+        if (!TextUtils.isEmpty(textBefore)) {
+            mStatistics.setIsEmptyUponStarting(false);
+            return;
+        }
+        final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(textAfter)) {
+            mStatistics.setIsEmptyUponStarting(false);
+            return;
+        }
+        if (textBefore != null && textAfter != null) {
+            mStatistics.setIsEmptyUponStarting(true);
+        }
+    }
+
     private void start() {
         maybeShowSplashScreen();
         updateSuspendedState();
         requestIndicatorRedraw();
+        mStatistics.reset();
+        checkForEmptyEditor();
         if (!isAllowedToLog()) {
             // Log.w(TAG, "not in usability mode; not logging");
             return;
@@ -295,6 +325,10 @@
     }
 
     /* package */ void stop() {
+        logStatistics();
+        publishLogUnit(mCurrentLogUnit, true);
+        mCurrentLogUnit = new LogUnit();
+
         if (mMainResearchLog != null) {
             mMainResearchLog.stop();
         }
@@ -303,6 +337,26 @@
         }
     }
 
+    private static final String[] EVENTKEYS_STATISTICS = {
+        "Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
+        "wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
+        "averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
+    };
+    private static void logStatistics() {
+        final ResearchLogger researchLogger = getInstance();
+        final Statistics statistics = researchLogger.mStatistics;
+        final Object[] values = {
+                statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
+                statistics.mSpaceCount, statistics.mDeleteKeyCount,
+                statistics.mWordCount, statistics.mIsEmptyUponStarting,
+                statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
+                statistics.mBeforeDeleteKeyCounter.getAverageTime(),
+                statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
+                statistics.mAfterDeleteKeyCounter.getAverageTime()
+        };
+        researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
+    }
+
     private void setLoggingAllowed(boolean enableLogging) {
         if (mPrefs == null) {
             return;
@@ -703,6 +757,7 @@
             mLoggingFrequencyState.onWordLogged();
         }
         mCurrentLogUnit = new LogUnit();
+        mStatistics.recordWordEntered();
     }
 
     private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
@@ -846,20 +901,6 @@
         stop();
     }
 
-    private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
-        "LatinIMECommitText", "typedWord"
-    };
-
-    public static void latinIME_commitText(final CharSequence typedWord) {
-        final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
-        final Object[] values = {
-            scrubbedWord
-        };
-        final ResearchLogger researchLogger = getInstance();
-        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
-        researchLogger.onWordComplete(scrubbedWord);
-    }
-
     private static final String[] EVENTKEYS_USER_FEEDBACK = {
         "UserFeedback", "FeedbackContents"
     };
@@ -911,48 +952,9 @@
         final Object[] values = {
             Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
         };
-        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
-    }
-
-    private static final String[] EVENTKEYS_CORRECTION = {
-        "LogCorrection", "subgroup", "before", "after", "position"
-    };
-    public static void logCorrection(final String subgroup, final String before, final String after,
-            final int position) {
-        final Object[] values = {
-            subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position
-        };
-        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = {
-        "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection"
-    };
-    public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
-            final String autoCorrection) {
-        final Object[] values = {
-            scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
-        };
         final ResearchLogger researchLogger = getInstance();
-        researchLogger.enqueuePotentiallyPrivateEvent(
-                EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
-        "LatinIMEDeleteSurroundingText", "length"
-    };
-    public static void latinIME_deleteSurroundingText(final int length) {
-        final Object[] values = {
-            length
-        };
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = {
-        "LatinIMEDoubleSpaceAutoPeriod"
-    };
-    public static void latinIME_doubleSpaceAutoPeriod() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES);
+        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+        researchLogger.mStatistics.recordChar(code, SystemClock.uptimeMillis());
     }
 
     private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
@@ -979,6 +981,10 @@
     public static void latinIME_onWindowHidden(final int savedSelectionStart,
             final int savedSelectionEnd, final InputConnection ic) {
         if (ic != null) {
+            // Capture the TextView contents.  This will trigger onUpdateSelection(), so we
+            // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
+            // it can tell that it was generated by the logging code, and not by the user, and
+            // therefore keep user-visible state as is.
             ic.beginBatchEdit();
             ic.performContextMenuAction(android.R.id.selectAll);
             CharSequence charSequence = ic.getSelectedText(0);
@@ -1048,29 +1054,6 @@
         researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
     }
 
-    private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = {
-        "LatinIMEPerformEditorAction", "imeActionNext"
-    };
-    public static void latinIME_performEditorAction(final int imeActionNext) {
-        final Object[] values = {
-            imeActionNext
-        };
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = {
-        "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
-    };
-    public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
-            final CharSequence cs, int x, int y) {
-        final Object[] values = {
-            index, cs, x, y
-        };
-        final ResearchLogger researchLogger = getInstance();
-        researchLogger.enqueuePotentiallyPrivateEvent(
-                EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
-    }
-
     private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
         "LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
     };
@@ -1096,21 +1079,6 @@
         getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
     }
 
-    private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = {
-        "LatinIMERevertDoubleSpaceWhileInBatchEdit"
-    };
-    public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT,
-                EVENTKEYS_NULLVALUES);
-    }
-
-    private static final String[] EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION = {
-        "LatinIMERevertSwapPunctuation"
-    };
-    public static void latinIME_revertSwapPunctuation() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES);
-    }
-
     private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
         "LatinIMESendKeyCodePoint", "code"
     };
@@ -1121,12 +1089,11 @@
         getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
     }
 
-    private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = {
-        "LatinIMESwapSwapperAndSpaceWhileInBatchEdit"
+    private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE = {
+        "LatinIMESwapSwapperAndSpace"
     };
-    public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
-        getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT,
-                EVENTKEYS_NULLVALUES);
+    public static void latinIME_swapSwapperAndSpace() {
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE, EVENTKEYS_NULLVALUES);
     }
 
     private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = {
@@ -1245,6 +1212,109 @@
         getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
     }
 
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION = {
+        "RichInputConnectionCommitCompletion", "completionInfo"
+    };
+    public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
+        final Object[] values = {
+            completionInfo
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(
+                EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION = {
+        "RichInputConnectionCommitCorrection", "CorrectionInfo"
+    };
+    public static void richInputConnection_commitCorrection(CorrectionInfo correctionInfo) {
+        final String typedWord = correctionInfo.getOldText().toString();
+        final String autoCorrection = correctionInfo.getNewText().toString();
+        final Object[] values = {
+            scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(
+                EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT = {
+        "RichInputConnectionCommitText", "typedWord", "newCursorPosition"
+    };
+    public static void richInputConnection_commitText(final CharSequence typedWord,
+            final int newCursorPosition) {
+        final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
+        final Object[] values = {
+            scrubbedWord, newCursorPosition
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT,
+                values);
+        researchLogger.onWordComplete(scrubbedWord);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = {
+        "RichInputConnectionDeleteSurroundingText", "beforeLength", "afterLength"
+    };
+    public static void richInputConnection_deleteSurroundingText(final int beforeLength,
+            final int afterLength) {
+        final Object[] values = {
+            beforeLength, afterLength
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = {
+        "RichInputConnectionFinishComposingText"
+    };
+    public static void richInputConnection_finishComposingText() {
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT,
+                EVENTKEYS_NULLVALUES);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION = {
+        "RichInputConnectionPerformEditorAction", "imeActionNext"
+    };
+    public static void richInputConnection_performEditorAction(final int imeActionNext) {
+        final Object[] values = {
+            imeActionNext
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT = {
+        "RichInputConnectionSendKeyEvent", "eventTime", "action", "code"
+    };
+    public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
+        final Object[] values = {
+            keyEvent.getEventTime(),
+            keyEvent.getAction(),
+            keyEvent.getKeyCode()
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = {
+        "RichInputConnectionSetComposingText", "text", "newCursorPosition"
+    };
+    public static void richInputConnection_setComposingText(final CharSequence text,
+            final int newCursorPosition) {
+        final Object[] values = {
+            text, newCursorPosition
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT, values);
+    }
+
+    private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION = {
+        "RichInputConnectionSetSelection", "from", "to"
+    };
+    public static void richInputConnection_setSelection(final int from, final int to) {
+        final Object[] values = {
+            from, to
+        };
+        getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION, values);
+    }
+
     private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
         "SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
     };
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
new file mode 100644
index 0000000..4a2cd07
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -0,0 +1,146 @@
+/*
+ * 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 com.android.inputmethod.keyboard.Keyboard;
+
+public class Statistics {
+    // Number of characters entered during a typing session
+    int mCharCount;
+    // Number of letter characters entered during a typing session
+    int mLetterCount;
+    // Number of number characters entered
+    int mNumberCount;
+    // Number of space characters entered
+    int mSpaceCount;
+    // Number of delete operations entered (taps on the backspace key)
+    int mDeleteKeyCount;
+    // Number of words entered during a session.
+    int mWordCount;
+    // Whether the text field was empty upon editing
+    boolean mIsEmptyUponStarting;
+    boolean mIsEmptinessStateKnown;
+
+    // Timers to count average time to enter a key, first press a delete key,
+    // between delete keys, and then to return typing after a delete key.
+    final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
+    final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
+    final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
+    final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
+
+    static class AverageTimeCounter {
+        int mCount;
+        int mTotalTime;
+
+        public void reset() {
+            mCount = 0;
+            mTotalTime = 0;
+        }
+
+        public void add(long deltaTime) {
+            mCount++;
+            mTotalTime += deltaTime;
+        }
+
+        public int getAverageTime() {
+            if (mCount == 0) {
+                return 0;
+            }
+            return mTotalTime / mCount;
+        }
+    }
+
+    // To account for the interruptions when the user's attention is directed elsewhere, times
+    // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
+    public static final int MIN_TYPING_INTERMISSION = 5 * 1000;  // in milliseconds
+    public static final int MIN_DELETION_INTERMISSION = 15 * 1000;  // in milliseconds
+
+    // The last time that a tap was performed
+    private long mLastTapTime;
+    // The type of the last keypress (delete key or not)
+    boolean mIsLastKeyDeleteKey;
+
+    private static final Statistics sInstance = new Statistics();
+
+    public static Statistics getInstance() {
+        return sInstance;
+    }
+
+    private Statistics() {
+        reset();
+    }
+
+    public void reset() {
+        mCharCount = 0;
+        mLetterCount = 0;
+        mNumberCount = 0;
+        mSpaceCount = 0;
+        mDeleteKeyCount = 0;
+        mWordCount = 0;
+        mIsEmptyUponStarting = true;
+        mIsEmptinessStateKnown = false;
+        mKeyCounter.reset();
+        mBeforeDeleteKeyCounter.reset();
+        mDuringRepeatedDeleteKeysCounter.reset();
+        mAfterDeleteKeyCounter.reset();
+
+        mLastTapTime = 0;
+        mIsLastKeyDeleteKey = false;
+    }
+
+    public void recordChar(int codePoint, long time) {
+        final long delta = time - mLastTapTime;
+        if (codePoint == Keyboard.CODE_DELETE) {
+            mDeleteKeyCount++;
+            if (delta < MIN_DELETION_INTERMISSION) {
+                if (mIsLastKeyDeleteKey) {
+                    mDuringRepeatedDeleteKeysCounter.add(delta);
+                } else {
+                    mBeforeDeleteKeyCounter.add(delta);
+                }
+            }
+            mIsLastKeyDeleteKey = true;
+        } else {
+            mCharCount++;
+            if (Character.isDigit(codePoint)) {
+                mNumberCount++;
+            }
+            if (Character.isLetter(codePoint)) {
+                mLetterCount++;
+            }
+            if (Character.isSpaceChar(codePoint)) {
+                mSpaceCount++;
+            }
+            if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
+                mAfterDeleteKeyCounter.add(delta);
+            } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
+                mKeyCounter.add(delta);
+            }
+            mIsLastKeyDeleteKey = false;
+        }
+        mLastTapTime = time;
+    }
+
+    public void recordWordEntered() {
+        mWordCount++;
+    }
+
+    public void setIsEmptyUponStarting(final boolean isEmpty) {
+        mIsEmptyUponStarting = isEmpty;
+        mIsEmptinessStateKnown = true;
+    }
+}