Promote touches in hit box according to the distance from sweet spot

Change-Id: Ice0fd0514304a79aed67627c2ea3439bd5177de4
diff --git a/native/src/correction.cpp b/native/src/correction.cpp
index 9e75ffc..d1e866d 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -118,9 +118,8 @@
     mInputIndex = mCorrectionStates[outputIndex].mInputIndex;
     mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes;
 
-    mEquivalentCharStrongCount = mCorrectionStates[outputIndex].mEquivalentCharStrongCount;
-    mEquivalentCharNormalCount = mCorrectionStates[outputIndex].mEquivalentCharNormalCount;
-    mEquivalentCharWeakCount = mCorrectionStates[outputIndex].mEquivalentCharWeakCount;
+    mSumOfDistance = mCorrectionStates[outputIndex].mSumOfDistance;
+    mEquivalentCharCount = mCorrectionStates[outputIndex].mEquivalentCharCount;
     mProximityCount = mCorrectionStates[outputIndex].mProximityCount;
     mTransposedCount = mCorrectionStates[outputIndex].mTransposedCount;
     mExcessiveCount = mCorrectionStates[outputIndex].mExcessiveCount;
@@ -175,9 +174,8 @@
     mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex;
     mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes;
 
-    mCorrectionStates[mOutputIndex].mEquivalentCharStrongCount = mEquivalentCharStrongCount;
-    mCorrectionStates[mOutputIndex].mEquivalentCharNormalCount = mEquivalentCharNormalCount;
-    mCorrectionStates[mOutputIndex].mEquivalentCharWeakCount = mEquivalentCharWeakCount;
+    mCorrectionStates[mOutputIndex].mSumOfDistance = mSumOfDistance;
+    mCorrectionStates[mOutputIndex].mEquivalentCharCount = mEquivalentCharCount;
     mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount;
     mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount;
     mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount;
@@ -220,9 +218,7 @@
 }
 
 inline bool isEquivalentChar(ProximityInfo::ProximityType type) {
-    // 'type ProximityInfo::EQUIVALENT_CHAR_WEAK' means that
-    // type == ..._WEAK or type == ..._NORMAL or type == ..._STRONG.
-    return type <= ProximityInfo::EQUIVALENT_CHAR_WEAK;
+    return type == ProximityInfo::EQUIVALENT_CHAR;
 }
 
 Correction::CorrectionType Correction::processCharAndCalcState(
@@ -304,7 +300,7 @@
     // TODO: Change the limit if we'll allow two or more proximity chars with corrections
     const bool checkProximityChars = noCorrectionsHappenedSoFar ||  mProximityCount == 0;
     const ProximityInfo::ProximityType matchedProximityCharId = secondTransposing
-            ? ProximityInfo::EQUIVALENT_CHAR_NORMAL
+            ? ProximityInfo::EQUIVALENT_CHAR
             : mProximityInfo->getMatchedProximityId(mInputIndex, c, checkProximityChars);
 
     if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
@@ -384,18 +380,14 @@
         mMatching = true;
     } else if (isEquivalentChar(matchedProximityCharId)) {
         mMatching = true;
-        switch (matchedProximityCharId) {
-        case ProximityInfo::EQUIVALENT_CHAR_STRONG:
-            ++mEquivalentCharStrongCount;
-            break;
-        case ProximityInfo::EQUIVALENT_CHAR_NORMAL:
-            ++mEquivalentCharNormalCount;
-            break;
-        case ProximityInfo::EQUIVALENT_CHAR_WEAK:
-            ++mEquivalentCharWeakCount;
-            break;
-        default:
-            assert(false);
+        ++mEquivalentCharCount;
+        if (mSumOfDistance != NOT_A_DISTANCE) {
+            const int distance = mProximityInfo->getNormalizedSquaredDistance(mInputIndex);
+            if (distance != NOT_A_DISTANCE) {
+                mSumOfDistance += distance;
+            } else {
+                mSumOfDistance = NOT_A_DISTANCE;
+            }
         }
     } else if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) {
         mProximityMatching = true;
@@ -568,8 +560,8 @@
     const int transposedCount = correction->mTransposedCount / 2;
     const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2;
     const int proximityMatchedCount = correction->mProximityCount;
-    const int equivalentCharStrongCount = correction->mEquivalentCharStrongCount;
-    const int equivalentCharWeakCount = correction->mEquivalentCharWeakCount;
+    const int mSumOfDistance = correction->mSumOfDistance;
+    const int mEquivalentCharCount = correction->mEquivalentCharCount;
     const bool lastCharExceeded = correction->mLastCharExceeded;
     const bool useFullEditDistance = correction->mUseFullEditDistance;
     const int outputLength = outputIndex + 1;
@@ -679,18 +671,37 @@
         multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
     }
 
-    for (int i = 0; i < equivalentCharStrongCount; ++i) {
-        if (DEBUG_DICT_FULL) {
-            LOGI("equivalent char strong");
-        }
-        multiplyRate(WORDS_WITH_EQUIVALENT_CHAR_STRONG_PROMOTION_RATE, &finalFreq);
-    }
-
-    for (int i = 0; i < equivalentCharWeakCount; ++i) {
-        if (DEBUG_DICT_FULL) {
-            LOGI("equivalent char weak");
-        }
-        multiplyRate(WORDS_WITH_EQUIVALENT_CHAR_WEAK_DEMOTION_RATE, &finalFreq);
+    if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES
+            && mEquivalentCharCount > 0 && mSumOfDistance != NOT_A_DISTANCE) {
+        // Let (x, y) be the coordinate of a user's touch, and let c be a key.
+        // Assuming users' touch distribution is gauss distribution, the conditional probability of
+        // the user touching (x, y) given he or she intends to hit c is:
+        //   p(x, y | c) = exp(-(x - m_x) / (2 * s^2)) / (sqrt(2 * pi) * s)
+        //               * exp(-(y - m_y) / (2 * s^2)) / (sqrt(2 * pi) * s)
+        // where (m_x, m_y) is a mean of touches of c, and s is a variance of touches of c.
+        // If user touches c1, c2, .., cn, the joint distribution is
+        //   p(x1, y1 | c1) * p(x2, y2 | c2) * ... * p(xn, yn | cn)
+        // We consider the logarithm of this value, that is
+        //     sum_i log p(x_i, y_i | c_i) + const
+        //   = sum_i ((x_i - m_x)^2 + (y_i - m_y)^2) / (2 * s^2) + const
+        // Thus, we use the sum of squared distance as a score of the word.
+        static const int UPPER = WORDS_WITH_EQUIVALENT_CHAR_STRONGEST_PROMOTION_RATE;
+        static const int LOWER = WORDS_WITH_EQUIVALENT_CHAR_WEAKEST_DEMOTION_RATE;
+        static const int MIDDLE = 100;
+        static const int SHIFT = ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
+        const int expected = mEquivalentCharCount << SHIFT;
+        // factor is a function as described below:
+        // U\            .
+        //   \           .
+        // M  \          .
+        //     \         .
+        // L    \------- .
+        //  0 e
+        // (x-axis is mSumOfDistance, y-axis is rate,
+        //  and e, U, M, L are expected, UPPER, MIDDLE, LOWER respectively.
+        const int factor =
+                max((UPPER * expected - (UPPER - MIDDLE) * mSumOfDistance) / expected, LOWER);
+        multiplyRate(factor, &finalFreq);
     }
 
     const int errorCount = adjustedProximityMatchedCount > 0
diff --git a/native/src/correction.h b/native/src/correction.h
index a630646..522c65f 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -127,9 +127,8 @@
     int mOutputIndex;
     int mInputIndex;
 
-    int mEquivalentCharStrongCount;
-    int mEquivalentCharNormalCount;
-    int mEquivalentCharWeakCount;
+    int mEquivalentCharCount;
+    int mSumOfDistance;
     int mProximityCount;
     int mExcessiveCount;
     int mTransposedCount;
diff --git a/native/src/correction_state.h b/native/src/correction_state.h
index a8ee82a..fff5cd5 100644
--- a/native/src/correction_state.h
+++ b/native/src/correction_state.h
@@ -29,9 +29,8 @@
     uint16_t mChildCount;
     uint8_t mInputIndex;
 
-    uint8_t mEquivalentCharStrongCount;
-    uint8_t mEquivalentCharNormalCount;
-    uint8_t mEquivalentCharWeakCount;
+    int32_t mSumOfDistance;
+    uint8_t mEquivalentCharCount;
     uint8_t mProximityCount;
     uint8_t mTransposedCount;
     uint8_t mExcessiveCount;
@@ -66,9 +65,8 @@
     state->mExcessivePos = -1;
     state->mSkipPos = -1;
 
-    state->mEquivalentCharStrongCount = 0;
-    state->mEquivalentCharNormalCount = 0;
-    state->mEquivalentCharWeakCount = 0;
+    state->mSumOfDistance = 0;
+    state->mEquivalentCharCount = 0;
     state->mProximityCount = 0;
     state->mTransposedCount = 0;
     state->mExcessiveCount = 0;
diff --git a/native/src/defines.h b/native/src/defines.h
index 57bd9f7..1941572 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -162,6 +162,7 @@
 #define NEW_DICTIONARY_HEADER_SIZE 5
 #define NOT_VALID_WORD -99
 #define NOT_A_CHARACTER -1
+#define NOT_A_DISTANCE -1
 
 #define KEYCODE_SPACE ' '
 
@@ -180,8 +181,8 @@
 #define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75
 #define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75
 #define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
-#define WORDS_WITH_EQUIVALENT_CHAR_STRONG_PROMOTION_RATE 105
-#define WORDS_WITH_EQUIVALENT_CHAR_WEAK_DEMOTION_RATE 95
+#define WORDS_WITH_EQUIVALENT_CHAR_STRONGEST_PROMOTION_RATE 110
+#define WORDS_WITH_EQUIVALENT_CHAR_WEAKEST_DEMOTION_RATE 90
 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120
 #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
 #define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index 4c21c2e..de3f421 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -115,41 +115,42 @@
     }
     mPrimaryInputWord[inputLength] = 0;
     for (int i = 0; i < mInputLength; ++i) {
-        mSweetSpotTypes[i] = calculateSweetSpotType(i);
+        float normalizedSquaredDistance = calculateNormalizedSquaredDistance(i);
+        if (normalizedSquaredDistance >= 0.0f) {
+            mNormalizedSquaredDistance[i] =
+                (int)(normalizedSquaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
+        } else {
+            mNormalizedSquaredDistance[i] = NOT_A_DISTANCE;
+        }
     }
 }
 
 inline float square(const float x) { return x * x; }
 
-ProximityInfo::SweetSpotType ProximityInfo::calculateSweetSpotType(int index) const {
+float ProximityInfo::calculateNormalizedSquaredDistance(int index) const {
+    static const float NOT_A_DISTANCE_FLOAT = -1.0f;
     if (KEY_COUNT == 0 || !mInputXCoordinates || !mInputYCoordinates) {
         // We do not have the coordinate data
-        return UNKNOWN;
+        return NOT_A_DISTANCE_FLOAT;
     }
     const int currentChar = getPrimaryCharAt(index);
     const unsigned short baseLowerC = Dictionary::toBaseLowerCase(currentChar);
     if (baseLowerC > MAX_CHAR_CODE) {
-        return UNKNOWN;
+        return NOT_A_DISTANCE_FLOAT;
     }
     const int keyIndex = mCodeToKeyIndex[baseLowerC];
     if (keyIndex < 0) {
-        return UNKNOWN;
+        return NOT_A_DISTANCE_FLOAT;
     }
     const float radius = mSweetSpotRadii[keyIndex];
     if (radius <= 0.0) {
         // When there are no calibration data for a key,
         // the radius of the key is assigned to zero.
-        return UNKNOWN;
+        return NOT_A_DISTANCE;
     }
     const float squaredRadius = square(radius);
     const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, index);
-    if (squaredDistance <= squaredRadius) {
-        return IN_SWEET_SPOT;
-    }
-    if (squaredDistance <= square(NEUTRAL_AREA_RADIUS_RATIO) * squaredRadius) {
-        return IN_NEUTRAL_AREA;
-    }
-    return OUT_OF_NEUTRAL_AREA;
+    return squaredDistance / squaredRadius;
 }
 
 float ProximityInfo::calculateSquaredDistanceFromSweetSpotCenter(
@@ -213,22 +214,7 @@
     // The first char in the array is what user typed. If it matches right away,
     // that means the user typed that same char for this pos.
     if (firstChar == baseLowerC || firstChar == c) {
-        if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES) {
-            switch (mSweetSpotTypes[index]) {
-            case UNKNOWN:
-                return EQUIVALENT_CHAR_NORMAL;
-            case IN_SWEET_SPOT:
-                return EQUIVALENT_CHAR_STRONG;
-            case IN_NEUTRAL_AREA:
-                return EQUIVALENT_CHAR_NORMAL;
-            case OUT_OF_NEUTRAL_AREA:
-                return EQUIVALENT_CHAR_WEAK;
-            default:
-                assert(false);
-            }
-        } else {
-            return EQUIVALENT_CHAR_NORMAL;
-        }
+        return EQUIVALENT_CHAR;
     }
 
     if (!checkProximityChars) return UNRELATED_CHAR;
@@ -266,6 +252,8 @@
     return true;
 }
 
+const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
+const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
 const int ProximityInfo::MAX_KEY_COUNT_IN_A_KEYBOARD;
 const int ProximityInfo::MAX_CHAR_CODE;
 
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 421ca0e..3425efe 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -27,14 +27,12 @@
 
 class ProximityInfo {
 public:
+    static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2 = 10;
+
     // Used as a return value for character comparison
     typedef enum {
-        // Same char, possibly with different case or accent, and in the sweet spot of the char
-        EQUIVALENT_CHAR_STRONG,
-        // Same char, possibly with different case or accent, and in the outer sweet spot
-        EQUIVALENT_CHAR_NORMAL,
-        // Same char, possibly with different case or accent, and in the hit box of the char
-        EQUIVALENT_CHAR_WEAK,
+        // Same char, possibly with different case or accent
+        EQUIVALENT_CHAR,
         // It is a char located nearby on the keyboard
         NEAR_PROXIMITY_CHAR,
         // It is an unrelated char
@@ -57,31 +55,25 @@
     bool existsAdjacentProximityChars(const int index) const;
     ProximityType getMatchedProximityId(
             const int index, const unsigned short c, const bool checkProximityChars) const;
+    int getNormalizedSquaredDistance(int index) const {
+        return mNormalizedSquaredDistance[index];
+    }
     bool sameAsTyped(const unsigned short *word, int length) const;
     const unsigned short* getPrimaryInputWord() const {
         return mPrimaryInputWord;
     }
 
 private:
+    static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR =
+            1 << NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
     // The max number of the keys in one keyboard layout
     static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64;
     // The upper limit of the char code in mCodeToKeyIndex
     static const int MAX_CHAR_CODE = 127;
 
-    typedef enum {
-        // cannot figure out the sweet spot type
-        UNKNOWN,
-        // touch position is out of neutral area of the given char
-        OUT_OF_NEUTRAL_AREA,
-        // touch position is in the neutral area of the given char
-        IN_NEUTRAL_AREA,
-        // touch position is in the sweet spot of the given char
-        IN_SWEET_SPOT
-    } SweetSpotType;
-
     int getStartIndexFromCoordinates(const int x, const int y) const;
     void initializeCodeToKeyIndex();
-    SweetSpotType calculateSweetSpotType(int index) const;
+    float calculateNormalizedSquaredDistance(int index) const;
     float calculateSquaredDistanceFromSweetSpotCenter(int keyIndex, int inputIndex) const;
 
     const int MAX_PROXIMITY_CHARS_SIZE;
@@ -104,7 +96,7 @@
     float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD];
-    SweetSpotType mSweetSpotTypes[MAX_WORD_LENGTH_INTERNAL];
+    int mNormalizedSquaredDistance[MAX_WORD_LENGTH_INTERNAL];
     int mInputLength;
     unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
     int mCodeToKeyIndex[MAX_CHAR_CODE + 1];