Merge "Revert "ResearchLog. Track time of log statements""
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 78c5f18..8d947b3 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -86,7 +86,7 @@
     <fraction name="config_gesture_sampling_minimum_distance">16.6666%</fraction>
     <!-- Parameters for gesture recognition (msec) and (keyWidth%/sec) -->
     <integer name="config_gesture_recognition_minimum_time">100</integer>
-    <integer name="config_gesture_recognition_update_time">300</integer>
+    <integer name="config_gesture_recognition_update_time">100</integer>
     <fraction name="config_gesture_recognition_speed_threshold">550%</fraction>
     <!-- Suppress showing key preview duration after batch input in millisecond -->
     <integer name="config_suppress_key_preview_after_batch_input_duration">1000</integer>
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 296a45f..c3cf49f 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -749,12 +749,12 @@
             if (getActivePointerTrackerCount() == 1) {
                 sInGesture = false;
                 sTimeRecorder.onEndBatchInput(eventTime);
+                mTimerProxy.cancelAllUpdateBatchInputTimers();
                 if (!mIsTrackingCanceled) {
                     if (DEBUG_LISTENER) {
                         Log.d(TAG, String.format("[%d] onEndBatchInput   : batchPoints=%d",
                                 mPointerId, sAggregratedPointers.getPointerSize()));
                     }
-                    mTimerProxy.cancelAllUpdateBatchInputTimers();
                     mListener.onEndBatchInput(sAggregratedPointers);
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 731b9ba..989451b 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
@@ -42,6 +43,7 @@
 
     private boolean mServiceNeedsRestart = false;
     private CheckBoxPreference mDebugMode;
+    private CheckBoxPreference mStatisticsLoggingPref;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -59,6 +61,7 @@
         }
         final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING_KEY);
         if (statisticsLoggingPref instanceof CheckBoxPreference) {
+            mStatisticsLoggingPref = (CheckBoxPreference) statisticsLoggingPref;
             if (!SHOW_STATISTICS_LOGGING) {
                 getPreferenceScreen().removePreference(statisticsLoggingPref);
             }
@@ -80,6 +83,14 @@
         if (key.equals(DEBUG_MODE_KEY)) {
             if (mDebugMode != null) {
                 mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false));
+                final boolean checked = mDebugMode.isChecked();
+                if (mStatisticsLoggingPref != null) {
+                    if (checked) {
+                        getPreferenceScreen().addPreference(mStatisticsLoggingPref);
+                    } else {
+                        getPreferenceScreen().removePreference(mStatisticsLoggingPref);
+                    }
+                }
                 updateDebugMode();
                 mServiceNeedsRestart = true;
             }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0b49659..8519ad3 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1464,7 +1464,7 @@
     private static final class BatchInputUpdater implements Handler.Callback {
         private final Handler mHandler;
         private LatinIME mLatinIme;
-        private boolean mInBatchInput; // synchornized using "this".
+        private boolean mInBatchInput; // synchronized using "this".
 
         private BatchInputUpdater() {
             final HandlerThread handlerThread = new HandlerThread(
@@ -1496,6 +1496,7 @@
 
         // Run in the UI thread.
         public synchronized void onStartBatchInput() {
+            mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
             mInBatchInput = true;
         }
 
@@ -1503,7 +1504,7 @@
         private synchronized void updateBatchInput(final InputPointers batchPointers,
                 final LatinIME latinIme) {
             if (!mInBatchInput) {
-                // Batch input has ended while the message was being delivered.
+                // Batch input has ended or canceled while the message was being delivered.
                 return;
             }
             final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(
@@ -1523,7 +1524,7 @@
                     .sendToTarget();
         }
 
-        public void onCancelBatchInput(final LatinIME latinIme) {
+        public synchronized void onCancelBatchInput(final LatinIME latinIme) {
             mInBatchInput = false;
             latinIme.mHandler.showGesturePreviewAndSuggestionStrip(
                     SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 745768d..94dbf39 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -16,16 +16,22 @@
 
 package com.android.inputmethod.research;
 
+import android.util.Log;
+
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.Suggest;
 
 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;
+
     // 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 = 18;
+    private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = DEBUG ? 2 : 18;
 
     private final ResearchLog mResearchLog;
     private Suggest mSuggest;
@@ -61,6 +67,9 @@
                 mWordsUntilSafeToSample--;
             }
         }
+        if (DEBUG) {
+            Log.d(TAG, "shiftedIn " + (newLogUnit.hasWord() ? newLogUnit.getWord() : ""));
+        }
     }
 
     public void resetWordCounter() {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index ea3f6fd..fe31039 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -60,6 +60,7 @@
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputConnection;
@@ -687,7 +688,8 @@
 
     /* package for test */ void commitCurrentLogUnit() {
         if (DEBUG) {
-            Log.d(TAG, "commitCurrentLogUnit");
+            Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasWord() ?
+                    ": " + mCurrentLogUnit.getWord() : ""));
         }
         if (!mCurrentLogUnit.isEmpty()) {
             if (mMainLogBuffer != null) {
@@ -808,8 +810,11 @@
     public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
             final SharedPreferences prefs) {
         final ResearchLogger researchLogger = getInstance();
-        researchLogger.start();
         if (editorInfo != null) {
+            final boolean isPassword = InputTypeUtils.isPasswordInputType(editorInfo.inputType)
+                    || InputTypeUtils.isVisiblePasswordInputType(editorInfo.inputType);
+            getInstance().setIsPasswordView(isPassword);
+            researchLogger.start();
             final Context context = researchLogger.mInputMethodService;
             try {
                 final PackageInfo packageInfo;
@@ -1076,7 +1081,6 @@
                 keyboard.mOccupiedHeight,
                 keyboard.mKeys
             };
-            getInstance().setIsPasswordView(isPasswordView);
             getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values);
         }
     }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index 98491bd..2065ab1 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -16,9 +16,14 @@
 
 package com.android.inputmethod.research;
 
+import android.util.Log;
+
 import com.android.inputmethod.latin.Constants;
 
 public class Statistics {
+    private static final String TAG = Statistics.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
     // Number of characters entered during a typing session
     int mCharCount;
     // Number of letter characters entered during a typing session
@@ -103,6 +108,9 @@
     }
 
     public void recordChar(int codePoint, long time) {
+        if (DEBUG) {
+            Log.d(TAG, "recordChar() called");
+        }
         final long delta = time - mLastTapTime;
         if (codePoint == Constants.CODE_DELETE) {
             mDeleteKeyCount++;
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 86410fb..d1e8316 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -17,7 +17,7 @@
 #ifndef LATINIME_BINARY_FORMAT_H
 #define LATINIME_BINARY_FORMAT_H
 
-#include <cctype>
+#include <cstdlib>
 #include <limits>
 #include <map>
 #include "bloom_filter.h"
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index c504cec..3809027 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -429,4 +429,10 @@
     // Additional proximity char which can differ by language.
     ADDITIONAL_PROXIMITY_CHAR
 } ProximityType;
+
+typedef enum {
+    NOT_A_DOUBLE_LETTER,
+    A_DOUBLE_LETTER,
+    A_STRONG_DOUBLE_LETTER
+} DoubleLetterLevel;
 #endif // LATINIME_DEFINES_H
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 740e1a2..a1e20cf 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -31,6 +31,10 @@
         1 << NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
 const float ProximityInfoState::NOT_A_DISTANCE_FLOAT = -1.0f;
 const int ProximityInfoState::NOT_A_CODE = -1;
+const int ProximityInfoState::LOOKUP_RADIUS_PERCENTILE = 50;
+const int ProximityInfoState::FIRST_POINT_TIME_OFFSET_MILLIS = 150;
+const int ProximityInfoState::STRONG_DOUBLE_LETTER_TIME_MILLIS = 600;
+const int ProximityInfoState::MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE = 5;
 
 void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
         const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize,
@@ -105,7 +109,7 @@
         mNearKeysVector.clear();
         mSearchKeysVector.clear();
         mSpeedRates.clear();
-        mBeelineSpeedRates.clear();
+        mBeelineSpeedPercentiles.clear();
         mCharProbabilities.clear();
         mDirections.clear();
     }
@@ -253,9 +257,9 @@
         AKLOGI("===== sampled points =====");
         for (int i = 0; i < mSampledInputSize; ++i) {
             if (isGeometric) {
-                AKLOGI("%d: x = %d, y = %d, time = %d, relative speed = %.4f, beeline speed = %.4f",
+                AKLOGI("%d: x = %d, y = %d, time = %d, relative speed = %.4f, beeline speed = %d",
                         i, mSampledInputXs[i], mSampledInputYs[i], mTimes[i], mSpeedRates[i],
-                        getBeelineSpeedRate(i));
+                        getBeelineSpeedPercentile(i));
             }
             sampledX << mSampledInputXs[i];
             sampledY << mSampledInputYs[i];
@@ -366,54 +370,65 @@
     }
 }
 
+static const int MAX_PERCENTILE = 100;
 void ProximityInfoState::refreshBeelineSpeedRates(const int inputSize,
         const int *const xCoordinates, const int *const yCoordinates, const int * times) {
-    mBeelineSpeedRates.resize(mSampledInputSize);
+    if (DEBUG_SAMPLING_POINTS){
+        AKLOGI("--- refresh beeline speed rates");
+    }
+    mBeelineSpeedPercentiles.resize(mSampledInputSize);
     for (int i = 0; i < mSampledInputSize; ++i) {
-        mBeelineSpeedRates[i] = calculateBeelineSpeedRate(
-                i, inputSize, xCoordinates, yCoordinates, times);
+        mBeelineSpeedPercentiles[i] = static_cast<int>(calculateBeelineSpeedRate(
+                i, inputSize, xCoordinates, yCoordinates, times) * MAX_PERCENTILE);
     }
 }
 
 float ProximityInfoState::calculateBeelineSpeedRate(
         const int id, const int inputSize, const int *const xCoordinates,
         const int *const yCoordinates, const int * times) const {
-    static const int MAX_PERCENTILE = 100;
-    static const int LOOKUP_TIME_PERCENTILE = 30;
-    static const int LOOKUP_RADIUS_PERCENTILE = 50;
-    if (mSampledInputSize <= 0 || mAverageSpeed < 0.1f) {
+    if (mSampledInputSize <= 0 || mAverageSpeed < 0.001f) {
+        if (DEBUG_SAMPLING_POINTS){
+            AKLOGI("--- invalid state: cancel. size = %d, ave = %f",
+                    mSampledInputSize, mAverageSpeed);
+        }
         return 1.0f;
     }
     const int lookupRadius =
             mProximityInfo->getMostCommonKeyWidth() * LOOKUP_RADIUS_PERCENTILE / MAX_PERCENTILE;
     const int x0 = mSampledInputXs[id];
     const int y0 = mSampledInputYs[id];
-    const int lookupTime =
-            (mTimes.back() - mTimes.front()) * LOOKUP_TIME_PERCENTILE / MAX_PERCENTILE;
-    if (lookupTime <= 0) {
-        return 1.0f;
-    }
+    const int actualInputIndex = mInputIndice[id];
     int tempTime = 0;
     int tempBeelineDistance = 0;
-    int start = mInputIndice[id];
+    int start = actualInputIndex;
     // lookup forward
-    while (start > 0 && tempTime < lookupTime && tempBeelineDistance < lookupRadius) {
+    while (start > 0 && tempBeelineDistance < lookupRadius) {
         tempTime += times[start] - times[start - 1];
         --start;
         tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[start], yCoordinates[start]);
     }
+    // Exclusive unless this is an edge point
+    if (start > 0 && start < actualInputIndex) {
+        ++start;
+    }
     tempTime= 0;
     tempBeelineDistance = 0;
-    int end = mInputIndice[id];
+    int end = actualInputIndex;
     // lookup backward
-    while (end < static_cast<int>(inputSize - 1) && tempTime < lookupTime
-            && tempBeelineDistance < lookupRadius) {
+    while (end < (inputSize - 1) && tempBeelineDistance < lookupRadius) {
         tempTime += times[end + 1] - times[end];
         ++end;
-        tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[start], yCoordinates[start]);
+        tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[end], yCoordinates[end]);
+    }
+    // Exclusive unless this is an edge point
+    if (end > actualInputIndex && end < (inputSize - 1)) {
+        --end;
     }
 
-    if (start == end) {
+    if (start >= end) {
+        if (DEBUG_DOUBLE_LETTER) {
+            AKLOGI("--- double letter: start == end %d", start);
+        }
         return 1.0f;
     }
 
@@ -422,11 +437,33 @@
     const int x3 = xCoordinates[end];
     const int y3 = yCoordinates[end];
     const int beelineDistance = getDistanceInt(x2, y2, x3, y3);
-    const int time = times[end] - times[start];
+    int adjustedStartTime = times[start];
+    if (start == 0 && actualInputIndex == 0 && inputSize > 1) {
+        adjustedStartTime += FIRST_POINT_TIME_OFFSET_MILLIS;
+    }
+    int adjustedEndTime = times[end];
+    if (end == (inputSize - 1) && inputSize > 1) {
+        adjustedEndTime -= FIRST_POINT_TIME_OFFSET_MILLIS;
+    }
+    const int time = adjustedEndTime - adjustedStartTime;
     if (time <= 0) {
         return 1.0f;
     }
-    return (static_cast<float>(beelineDistance) / static_cast<float>(time)) / mAverageSpeed;
+
+    if (time >= STRONG_DOUBLE_LETTER_TIME_MILLIS){
+        return 0.0f;
+    }
+    if (DEBUG_DOUBLE_LETTER) {
+        AKLOGI("--- (%d, %d) double letter: start = %d, end = %d, dist = %d, time = %d, speed = %f,"
+                " ave = %f, val = %f, start time = %d, end time = %d",
+                id, mInputIndice[id], start, end, beelineDistance, time,
+                (static_cast<float>(beelineDistance) / static_cast<float>(time)), mAverageSpeed,
+                ((static_cast<float>(beelineDistance) / static_cast<float>(time)) / mAverageSpeed),
+                adjustedStartTime, adjustedEndTime);
+    }
+    // Offset 1%
+    // TODO: Detect double letter more smartly
+    return 0.01f + static_cast<float>(beelineDistance) / static_cast<float>(time) / mAverageSpeed;
 }
 
 bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSize,
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 97281af..5f968e1 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -39,6 +39,10 @@
     static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
     static const float NOT_A_DISTANCE_FLOAT;
     static const int NOT_A_CODE;
+    static const int LOOKUP_RADIUS_PERCENTILE;
+    static const int FIRST_POINT_TIME_OFFSET_MILLIS;
+    static const int STRONG_DOUBLE_LETTER_TIME_MILLIS;
+    static const int MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE;
 
     /////////////////////////////////////////
     // Defined in proximity_info_state.cpp //
@@ -56,8 +60,8 @@
               mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(),
               mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
               mIsContinuationPossible(false), mSampledInputXs(), mSampledInputYs(), mTimes(),
-              mInputIndice(), mLengthCache(), mDistanceCache(), mSpeedRates(),
-              mDirections(), mBeelineSpeedRates(), mCharProbabilities(), mNearKeysVector(),
+              mInputIndice(), mLengthCache(), mBeelineSpeedPercentiles(), mDistanceCache(),
+              mSpeedRates(), mDirections(), mCharProbabilities(), mNearKeysVector(),
               mSearchKeysVector(), mTouchPositionCorrectionEnabled(false), mSampledInputSize(0) {
         memset(mInputCodes, 0, sizeof(mInputCodes));
         memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
@@ -167,8 +171,19 @@
         return mSpeedRates[index];
     }
 
-    AK_FORCE_INLINE float getBeelineSpeedRate(const int id) const {
-        return mBeelineSpeedRates[id];
+    AK_FORCE_INLINE int getBeelineSpeedPercentile(const int id) const {
+        return mBeelineSpeedPercentiles[id];
+    }
+
+    AK_FORCE_INLINE DoubleLetterLevel getDoubleLetterLevel(const int id) const {
+        const int beelineSpeedRate = getBeelineSpeedPercentile(id);
+        if (beelineSpeedRate == 0) {
+            return A_STRONG_DOUBLE_LETTER;
+        } else if (beelineSpeedRate < MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE) {
+            return A_DOUBLE_LETTER;
+        } else {
+            return NOT_A_DOUBLE_LETTER;
+        }
     }
 
     float getDirection(const int index) const {
@@ -259,10 +274,10 @@
     std::vector<int> mTimes;
     std::vector<int> mInputIndice;
     std::vector<int> mLengthCache;
+    std::vector<int> mBeelineSpeedPercentiles;
     std::vector<float> mDistanceCache;
     std::vector<float> mSpeedRates;
     std::vector<float> mDirections;
-    std::vector<float> mBeelineSpeedRates;
     // probabilities of skipping or mapping to a key for each point.
     std::vector<hash_map_compat<int, float> > mCharProbabilities;
     // The vector for the key code set which holds nearby keys for each sampled input point