Merge "Remove entry for obsolete resource"
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 218ca22..19076d2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -820,10 +820,6 @@
         super.onFinishInput();
 
         LatinImeLogger.commit();
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.getInstance().latinIME_onFinishInputInternal();
-        }
-
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
             mainKeyboardView.closing();
@@ -840,6 +836,9 @@
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestionStrip();
         resetComposingState(true /* alsoResetLastComposedWord */);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.getInstance().latinIME_onFinishInputViewInternal();
+        }
     }
 
     @Override
@@ -1628,6 +1627,9 @@
         mConnection.setComposingText(batchInputText, 1);
         mExpectingUpdateSelection = true;
         mConnection.endBatchEdit();
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0);
+        }
         // Space state must be updated before calling updateShiftState
         mSpaceState = SPACE_STATE_PHANTOM;
         mKeyboardSwitcher.updateShiftState();
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index 3e542a4..ab9d2f8 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -62,12 +62,16 @@
         mIsPartOfMegaword = isPartOfMegaword;
     }
 
+    private static final Object[] NULL_VALUES = new Object[0];
     /**
      * Adds a new log statement.  The time parameter in successive calls to this method must be
      * monotonically increasing, or splitByTime() will not work.
      */
-    public void addLogStatement(final LogStatement logStatement, final Object[] values,
-            final long time) {
+    public void addLogStatement(final LogStatement logStatement, final long time,
+            Object... values) {
+        if (values == null) {
+            values = NULL_VALUES;
+        }
         mLogStatementList.add(logStatement);
         mValuesList.add(values);
         mTimeList.add(time);
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 12c189f..b027643 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -119,6 +119,10 @@
             } else {
                 // Words not in the dictionary are a privacy threat.
                 if (ResearchLogger.hasLetters(word) && !(dictionary.isValidWord(word))) {
+                    if (DEBUG) {
+                        Log.d(TAG, "NOT SAFE!: hasLetters: " + ResearchLogger.hasLetters(word)
+                                + ", isValid: " + (dictionary.isValidWord(word)));
+                    }
                     return false;
                 }
             }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 9ea46ee..38c3001 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -84,7 +84,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 OUTPUT_ENTIRE_BUFFER = false;  // true may disclose private info
+    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;
     private static final int OUTPUT_FORMAT_VERSION = 5;
@@ -136,7 +136,6 @@
 
     // used to check whether words are not unique
     private Suggest mSuggest;
-    private Dictionary mDictionary;
     private MainKeyboardView mMainKeyboardView;
     private InputMethodService mInputMethodService;
     private final Statistics mStatistics;
@@ -379,7 +378,8 @@
         commitCurrentLogUnit();
 
         if (mMainLogBuffer != null) {
-            publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */);
+            publishLogBuffer(mMainLogBuffer, mMainResearchLog,
+                    LOG_EVERYTHING /* isIncludingPrivateData */);
             mMainResearchLog.close(null /* callback */);
             mMainLogBuffer = null;
         }
@@ -565,10 +565,8 @@
             mFeedbackLogBuffer.clear();
         }
         final LogUnit feedbackLogUnit = new LogUnit();
-        final Object[] values = {
-            feedbackContents
-        };
-        feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, values, SystemClock.uptimeMillis());
+        feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(),
+                feedbackContents);
         mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
         publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
         mFeedbackLog.close(new Runnable() {
@@ -598,6 +596,13 @@
         }
     }
 
+    private Dictionary getDictionary() {
+        if (mSuggest == null) {
+            return null;
+        }
+        return mSuggest.getMainDictionary();
+    }
+
     private void setIsPasswordView(boolean isPasswordView) {
         mIsPasswordView = isPasswordView;
     }
@@ -653,19 +658,14 @@
         }
     }
 
-    private static final Object[] NULL_VALUES = {};
-
     /**
      * Buffer a research log event, flagging it as privacy-sensitive.
      */
     private synchronized void enqueueEvent(LogStatement logStatement, Object... values) {
-        if (values == null) {
-            values = NULL_VALUES;
-        }
         assert values.length == logStatement.mKeys.length;
         if (isAllowedToLog()) {
             final long time = SystemClock.uptimeMillis();
-            mCurrentLogUnit.addLogStatement(logStatement, values, time);
+            mCurrentLogUnit.addLogStatement(logStatement, time, values);
         }
     }
 
@@ -701,11 +701,8 @@
     /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
             final ResearchLog researchLog, final boolean isIncludingPrivateData) {
         final LogUnit openingLogUnit = new LogUnit();
-        final Object[] values = {
-            isIncludingPrivateData
-        };
-        openingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_OPENING, values,
-                SystemClock.uptimeMillis());
+        openingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_OPENING, SystemClock.uptimeMillis(),
+                isIncludingPrivateData);
         researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */);
         LogUnit logUnit;
         while ((logUnit = logBuffer.shiftOut()) != null) {
@@ -716,7 +713,7 @@
             researchLog.publish(logUnit, isIncludingPrivateData);
         }
         final LogUnit closingLogUnit = new LogUnit();
-        closingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_CLOSING, NULL_VALUES,
+        closingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_CLOSING,
                 SystemClock.uptimeMillis());
         researchLog.publish(closingLogUnit, true /* isIncludingPrivateData */);
     }
@@ -732,19 +729,22 @@
         return false;
     }
 
-    private static final LogStatement LOGSTATEMENT_COMMIT_AUTOSPACE =
-            new LogStatement("CommitAutospace", true, false);
-    private void onWordComplete(final String word, final long maxTime, final boolean isPartial) {
-        Log.d(TAG, "onWordComplete: " + word);
+    private static final LogStatement LOGSTATEMENT_COMMIT_RECORD_SPLIT_WORDS =
+            new LogStatement("recordSplitWords", true, false);
+    private void onWordComplete(final String word, final long maxTime, final boolean isSplitWords) {
+        final Dictionary dictionary = getDictionary();
         if (word != null && word.length() > 0 && hasLetters(word)) {
             mCurrentLogUnit.setWord(word);
-            mStatistics.recordWordEntered();
+            final boolean isDictionaryWord = dictionary != null
+                    && dictionary.isValidWord(word);
+            mStatistics.recordWordEntered(isDictionaryWord);
         }
         final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime);
         enqueueCommitText(word);
-        if (isPartial) {
-            enqueueEvent(LOGSTATEMENT_COMMIT_AUTOSPACE, NULL_VALUES);
+        if (isSplitWords) {
+            enqueueEvent(LOGSTATEMENT_COMMIT_RECORD_SPLIT_WORDS);
             enqueueCommitText(" ");
+            mStatistics.recordSplitWords();
         }
         commitCurrentLogUnit();
         mCurrentLogUnit = newLogUnit;
@@ -791,10 +791,11 @@
     }
 
     private String scrubWord(String word) {
-        if (mDictionary == null) {
+        final Dictionary dictionary = getDictionary();
+        if (dictionary == null) {
             return WORD_REPLACEMENT_STRING;
         }
-        if (mDictionary.isValidWord(word)) {
+        if (dictionary.isValidWord(word)) {
             return word;
         }
         return WORD_REPLACEMENT_STRING;
@@ -803,7 +804,7 @@
     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");
+                    "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything");
     public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
             final SharedPreferences prefs) {
         final ResearchLogger researchLogger = getInstance();
@@ -824,14 +825,14 @@
                         Integer.toHexString(editorInfo.inputType),
                         Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
                         Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
-                        OUTPUT_FORMAT_VERSION);
+                        OUTPUT_FORMAT_VERSION, LOG_EVERYTHING);
             } catch (NameNotFoundException e) {
                 e.printStackTrace();
             }
         }
     }
 
-    public void latinIME_onFinishInputInternal() {
+    public void latinIME_onFinishInputViewInternal() {
         stop();
     }
 
@@ -904,7 +905,7 @@
         if (ic != null) {
             final boolean isTextTruncated;
             final String text;
-            if (OUTPUT_ENTIRE_BUFFER) {
+            if (LOG_EVERYTHING) {
                 // 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
@@ -1205,11 +1206,23 @@
         getInstance().enqueueEvent(LOGSTATEMENT_USER_TIMESTAMP);
     }
 
+    private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT =
+            new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText",
+                    "enteredWordPos");
+    public static void latinIME_onEndBatchInput(final CharSequence enteredText,
+            final int enteredWordPos) {
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
+                enteredWordPos);
+        researchLogger.mStatistics.recordGestureInput();
+    }
+
     private static final LogStatement LOGSTATEMENT_STATISTICS =
             new LogStatement("Statistics", false, false, "charCount", "letterCount", "numberCount",
                     "spaceCount", "deleteOpsCount", "wordCount", "isEmptyUponStarting",
                     "isEmptinessStateKnown", "averageTimeBetweenKeys", "averageTimeBeforeDelete",
-                    "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete");
+                    "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
+                    "dictionaryWordCount", "splitWordsCount", "gestureInputCount");
     private static void logStatistics() {
         final ResearchLogger researchLogger = getInstance();
         final Statistics statistics = researchLogger.mStatistics;
@@ -1219,6 +1232,8 @@
                 statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
                 statistics.mBeforeDeleteKeyCounter.getAverageTime(),
                 statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
-                statistics.mAfterDeleteKeyCounter.getAverageTime());
+                statistics.mAfterDeleteKeyCounter.getAverageTime(),
+                statistics.mDictionaryWordCount, statistics.mSplitWordsCount,
+                statistics.mGestureInputCount);
     }
 }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index 2065ab1..90d7f38 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -36,6 +36,12 @@
     int mDeleteKeyCount;
     // Number of words entered during a session.
     int mWordCount;
+    // Number of words found in the dictionary.
+    int mDictionaryWordCount;
+    // Number of words split and spaces automatically entered.
+    int mSplitWordsCount;
+    // Number of gestures that were input.
+    int mGestureInputCount;
     // Whether the text field was empty upon editing
     boolean mIsEmptyUponStarting;
     boolean mIsEmptinessStateKnown;
@@ -143,8 +149,19 @@
         mLastTapTime = time;
     }
 
-    public void recordWordEntered() {
+    public void recordWordEntered(final boolean isDictionaryWord) {
         mWordCount++;
+        if (isDictionaryWord) {
+            mDictionaryWordCount++;
+        }
+    }
+
+    public void recordSplitWords() {
+        mSplitWordsCount++;
+    }
+
+    public void recordGestureInput() {
+        mGestureInputCount++;
     }
 
     public void setIsEmptyUponStarting(final boolean isEmpty) {