Merge "Offer to add any OOV typed word to the dictionary."
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 18a8023..56b1c78 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1906,7 +1906,6 @@
     private boolean handleSeparator(final int primaryCode, final int x, final int y,
             final int spaceState) {
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.recordTimeForLogUnitSplit();
             ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
         }
         boolean didAutoCorrect = false;
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index dcb514a..59ad28f 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -115,11 +115,12 @@
         // - This does not work for Greek, because it returns upper case instead of title case.
         // - It does not work for Serbian, because it fails to account for the "lj" character,
         // which should be "Lj" in title case and "LJ" in upper case.
-        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
-        // are two different characters but both should be capitalized as "IJ" as if they were
-        // a single letter.
-        // - It also does not work with unicode surrogate code points.
-        return s.toUpperCase(locale).charAt(0) + s.substring(1);
+        // - It does not work for Dutch, because it fails to account for the "ij" digraph when it's
+        // written as two separate code points. They are two different characters but both should
+        // be capitalized as "IJ" as if they were a single letter in most words (not all). If the
+        // unicode char for the ligature is used however, it works.
+        final int cutoff = s.offsetByCodePoints(0, 1);
+        return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff).toLowerCase(locale);
     }
 
     private static final int[] EMPTY_CODEPOINTS = {};
@@ -176,17 +177,27 @@
         return list.toArray(new String[list.size()]);
     }
 
-    // This method assumes the text is not empty or null.
+    // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE.
     public static int getCapitalizationType(final String text) {
         // If the first char is not uppercase, then the word is either all lower case or
         // camel case, and in either case we return CAPITALIZE_NONE.
-        if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
         final int len = text.length();
+        int index = 0;
+        for (; index < len; index = text.offsetByCodePoints(index, 1)) {
+            if (Character.isLetter(text.codePointAt(index))) {
+                break;
+            }
+        }
+        if (index == len) return CAPITALIZE_NONE;
+        if (!Character.isUpperCase(text.codePointAt(index))) {
+            return CAPITALIZE_NONE;
+        }
         int capsCount = 1;
         int letterCount = 1;
-        for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) {
+        for (index = text.offsetByCodePoints(index, 1); index < len;
+                index = text.offsetByCodePoints(index, 1)) {
             if (1 != capsCount && letterCount != capsCount) break;
-            final int codePoint = text.codePointAt(i);
+            final int codePoint = text.codePointAt(index);
             if (Character.isUpperCase(codePoint)) {
                 ++capsCount;
                 ++letterCount;
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index fbfa9c9..fa124f3 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -790,8 +790,7 @@
     }
 
     private boolean isAllowedToLog() {
-        return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog
-                && !isReplaying();
+        return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
     }
 
     public void requestIndicatorRedraw() {
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 74b5e01..50f38e8 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -129,7 +129,7 @@
 }
 
 float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG(
-        const int keyId, const int x, const int y) const {
+        const int keyId, const int x, const int y, const float verticalScale) const {
     const bool correctTouchPosition = hasTouchPositionCorrectionData();
     const float centerX = static_cast<float>(correctTouchPosition ? getSweetSpotCenterXAt(keyId)
             : getKeyCenterXOfKeyIdG(keyId));
@@ -138,7 +138,7 @@
     if (correctTouchPosition) {
         const float sweetSpotCenterY = static_cast<float>(getSweetSpotCenterYAt(keyId));
         const float gapY = sweetSpotCenterY - visualKeyCenterY;
-        centerY = visualKeyCenterY + gapY * ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G;
+        centerY = visualKeyCenterY + gapY * verticalScale;
     } else {
         centerY = visualKeyCenterY;
     }
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index 57a175d..e21262f 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -39,7 +39,8 @@
     bool hasSpaceProximity(const int x, const int y) const;
     int getNormalizedSquaredDistance(const int inputIndex, const int proximityIndex) const;
     float getNormalizedSquaredDistanceFromCenterFloatG(
-            const int keyId, const int x, const int y) const;
+            const int keyId, const int x, const int y,
+            const float verticalScale) const;
     bool sameAsTyped(const unsigned short *word, int length) const;
     int getCodePointOf(const int keyIndex) const;
     bool hasSweetSpotData(const int keyIndex) const {
diff --git a/native/jni/src/proximity_info_params.cpp b/native/jni/src/proximity_info_params.cpp
index f9a4352..2675d9e 100644
--- a/native/jni/src/proximity_info_params.cpp
+++ b/native/jni/src/proximity_info_params.cpp
@@ -20,7 +20,8 @@
 namespace latinime {
 const float ProximityInfoParams::NOT_A_DISTANCE_FLOAT = -1.0f;
 const int ProximityInfoParams::MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE = 5;
-const float ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G = 1.1f;
+const float ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE = 1.0f;
+const float ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G = 0.5f;
 
 /* Per method constants */
 // Used by ProximityInfoStateUtils::initGeometricDistanceInfos()
diff --git a/native/jni/src/proximity_info_params.h b/native/jni/src/proximity_info_params.h
index e7aec09..4e47f73 100644
--- a/native/jni/src/proximity_info_params.h
+++ b/native/jni/src/proximity_info_params.h
@@ -25,6 +25,7 @@
  public:
     static const float NOT_A_DISTANCE_FLOAT;
     static const int MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE;
+    static const float VERTICAL_SWEET_SPOT_SCALE;
     static const float VERTICAL_SWEET_SPOT_SCALE_G;
 
     // Used by ProximityInfoStateUtils::initGeometricDistanceInfos()
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 861ba99..a10b260 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -28,6 +28,7 @@
 
 namespace latinime {
 
+// TODO: Remove the dependency of "isGeometric"
 void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
         const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize,
         const int *const xCoordinates, const int *const yCoordinates, const int *const times,
@@ -94,12 +95,17 @@
                 pushTouchPointStartIndex, lastSavedInputSize);
     }
 
+    // TODO: Remove the dependency of "isGeometric"
+    const float verticalSweetSpotScale = isGeometric
+            ? ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G
+            : ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE;
+
     if (xCoordinates && yCoordinates) {
         mSampledInputSize = ProximityInfoStateUtils::updateTouchPoints(mProximityInfo,
                 mMaxPointToKeyLength, mInputProximities, xCoordinates, yCoordinates, times,
-                pointerIds, inputSize, isGeometric, pointerId, pushTouchPointStartIndex,
-                &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache,
-                &mSampledInputIndice);
+                pointerIds, verticalSweetSpotScale, inputSize, isGeometric, pointerId,
+                pushTouchPointStartIndex, &mSampledInputXs, &mSampledInputYs, &mSampledTimes,
+                &mSampledLengthCache, &mSampledInputIndice);
     }
 
     if (mSampledInputSize > 0 && isGeometric) {
@@ -115,8 +121,8 @@
 
     if (mSampledInputSize > 0) {
         ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize,
-                lastSavedInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledNearKeySets,
-                &mSampledDistanceCache_G);
+                lastSavedInputSize, verticalSweetSpotScale, &mSampledInputXs, &mSampledInputYs,
+                &mSampledNearKeySets, &mSampledDistanceCache_G);
         if (isGeometric) {
             // updates probabilities of skipping or mapping each key for all points.
             ProximityInfoStateUtils::updateAlignPointProbabilities(
diff --git a/native/jni/src/proximity_info_state_utils.cpp b/native/jni/src/proximity_info_state_utils.cpp
index 7605080..df70cff 100644
--- a/native/jni/src/proximity_info_state_utils.cpp
+++ b/native/jni/src/proximity_info_state_utils.cpp
@@ -42,8 +42,8 @@
         const ProximityInfo *const proximityInfo, const int maxPointToKeyLength,
         const int *const inputProximities, const int *const inputXCoordinates,
         const int *const inputYCoordinates, const int *const times, const int *const pointerIds,
-        const int inputSize, const bool isGeometric, const int pointerId,
-        const int pushTouchPointStartIndex, std::vector<int> *sampledInputXs,
+        const float verticalSweetSpotScale, const int inputSize, const bool isGeometric,
+        const int pointerId, const int pushTouchPointStartIndex, std::vector<int> *sampledInputXs,
         std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
         std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice) {
     if (DEBUG_SAMPLING_POINTS) {
@@ -112,10 +112,10 @@
             }
 
             if (pushTouchPoint(proximityInfo, maxPointToKeyLength, i, c, x, y, time,
-                    isGeometric /* doSampling */, i == lastInputIndex, sumAngle,
-                    currentNearKeysDistances, prevNearKeysDistances, prevPrevNearKeysDistances,
-                    sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
-                    sampledInputIndice)) {
+                    verticalSweetSpotScale, isGeometric /* doSampling */, i == lastInputIndex,
+                    sumAngle, currentNearKeysDistances, prevNearKeysDistances,
+                    prevPrevNearKeysDistances, sampledInputXs, sampledInputYs, sampledInputTimes,
+                    sampledLengthCache, sampledInputIndice)) {
                 // Previous point information was popped.
                 NearKeysDistanceMap *tmp = prevNearKeysDistances;
                 prevNearKeysDistances = currentNearKeysDistances;
@@ -222,7 +222,8 @@
 
 /* static */ void ProximityInfoStateUtils::initGeometricDistanceInfos(
         const ProximityInfo *const proximityInfo, const int sampledInputSize,
-        const int lastSavedInputSize, const std::vector<int> *const sampledInputXs,
+        const int lastSavedInputSize, const float verticalSweetSpotScale,
+        const std::vector<int> *const sampledInputXs,
         const std::vector<int> *const sampledInputYs,
         std::vector<NearKeycodesSet> *SampledNearKeySets,
         std::vector<float> *SampledDistanceCache_G) {
@@ -236,7 +237,8 @@
             const int x = (*sampledInputXs)[i];
             const int y = (*sampledInputYs)[i];
             const float normalizedSquaredDistance =
-                    proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y);
+                    proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(
+                            k, x, y, verticalSweetSpotScale);
             (*SampledDistanceCache_G)[index] = normalizedSquaredDistance;
             if (normalizedSquaredDistance
                     < ProximityInfoParams::NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD) {
@@ -354,12 +356,14 @@
 // the given point and the nearest key position.
 /* static */ float ProximityInfoStateUtils::updateNearKeysDistances(
         const ProximityInfo *const proximityInfo, const float maxPointToKeyLength, const int x,
-        const int y, NearKeysDistanceMap *const currentNearKeysDistances) {
+        const int y, const float verticalSweetspotScale,
+        NearKeysDistanceMap *const currentNearKeysDistances) {
     currentNearKeysDistances->clear();
     const int keyCount = proximityInfo->getKeyCount();
     float nearestKeyDistance = maxPointToKeyLength;
     for (int k = 0; k < keyCount; ++k) {
-        const float dist = proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y);
+        const float dist = proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y,
+                verticalSweetspotScale);
         if (dist < ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_DISTANCE) {
             currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
         }
@@ -439,7 +443,8 @@
 // Returning if previous point is popped or not.
 /* static */ bool ProximityInfoStateUtils::pushTouchPoint(const ProximityInfo *const proximityInfo,
         const int maxPointToKeyLength, const int inputIndex, const int nodeCodePoint, int x, int y,
-        const int time, const bool doSampling, const bool isLastPoint, const float sumAngle,
+        const int time, const float verticalSweetSpotScale, const bool doSampling,
+        const bool isLastPoint, const float sumAngle,
         NearKeysDistanceMap *const currentNearKeysDistances,
         const NearKeysDistanceMap *const prevNearKeysDistances,
         const NearKeysDistanceMap *const prevPrevNearKeysDistances,
@@ -451,8 +456,8 @@
     size_t size = sampledInputXs->size();
     bool popped = false;
     if (nodeCodePoint < 0 && doSampling) {
-        const float nearest = updateNearKeysDistances(
-                proximityInfo, maxPointToKeyLength, x, y, currentNearKeysDistances);
+        const float nearest = updateNearKeysDistances(proximityInfo, maxPointToKeyLength, x, y,
+                verticalSweetSpotScale, currentNearKeysDistances);
         const float score = getPointScore(mostCommonKeyWidth, x, y, time, isLastPoint, nearest,
                 sumAngle, currentNearKeysDistances, prevNearKeysDistances,
                 prevPrevNearKeysDistances, sampledInputXs, sampledInputYs);
diff --git a/native/jni/src/proximity_info_state_utils.h b/native/jni/src/proximity_info_state_utils.h
index 3ceb25d..c9feb59 100644
--- a/native/jni/src/proximity_info_state_utils.h
+++ b/native/jni/src/proximity_info_state_utils.h
@@ -38,7 +38,8 @@
     static int updateTouchPoints(const ProximityInfo *const proximityInfo,
             const int maxPointToKeyLength, const int *const inputProximities,
             const int *const inputXCoordinates, const int *const inputYCoordinates,
-            const int *const times, const int *const pointerIds, const int inputSize,
+            const int *const times, const int *const pointerIds,
+            const float verticalSweetSpotScale, const int inputSize,
             const bool isGeometric, const int pointerId, const int pushTouchPointStartIndex,
             std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
             std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
@@ -84,6 +85,7 @@
             const int inputIndex, const int keyId);
     static void initGeometricDistanceInfos(const ProximityInfo *const proximityInfo,
             const int sampledInputSize, const int lastSavedInputSize,
+            const float verticalSweetSpotScale,
             const std::vector<int> *const sampledInputXs,
             const std::vector<int> *const sampledInputYs,
             std::vector<NearKeycodesSet> *SampledNearKeySets,
@@ -118,6 +120,7 @@
 
     static float updateNearKeysDistances(const ProximityInfo *const proximityInfo,
             const float maxPointToKeyLength, const int x, const int y,
+            const float verticalSweetSpotScale,
             NearKeysDistanceMap *const currentNearKeysDistances);
     static bool isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
             const NearKeysDistanceMap *const prevNearKeysDistances,
@@ -130,7 +133,8 @@
             std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs);
     static bool pushTouchPoint(const ProximityInfo *const proximityInfo,
             const int maxPointToKeyLength, const int inputIndex, const int nodeCodePoint, int x,
-            int y, const int time, const bool doSampling, const bool isLastPoint,
+            int y, const int time, const float verticalSweetSpotScale,
+            const bool doSampling, const bool isLastPoint,
             const float sumAngle, NearKeysDistanceMap *const currentNearKeysDistances,
             const NearKeysDistanceMap *const prevNearKeysDistances,
             const NearKeysDistanceMap *const prevPrevNearKeysDistances,
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 7bfa459..cde7b99 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -48,13 +48,6 @@
 
 namespace latinime {
 
-// Naming convention
-// - Distance: "Weighted" edit distance -- used both for spatial and language.
-// - Compound Distance: Spatial Distance + Language Distance -- used for pruning and scoring
-// - Cost: delta/diff for Distance -- used both for spatial and language
-// - Length: "Non-weighted" -- used only for spatial
-// - Probability: "Non-weighted" -- used only for language
-
 // This struct is purely a bucket to return values. No instances of this struct should be kept.
 struct DicNode_InputStateG {
     bool mNeedsToUpdateInputStateG;
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 1e97a91..764c372 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -426,7 +426,6 @@
         weightChildNode(traverseSession, childDicNode);
 
         if (!TRAVERSAL->isPossibleOmissionChildNode(traverseSession, dicNode, childDicNode)) {
-            DicNode::managedDelete(childDicNode);
             continue;
         }
         processExpandedDicNode(traverseSession, childDicNode);
diff --git a/native/jni/src/suggest/core/suggest.h b/native/jni/src/suggest/core/suggest.h
index a1e7e7a..6c09b94 100644
--- a/native/jni/src/suggest/core/suggest.h
+++ b/native/jni/src/suggest/core/suggest.h
@@ -23,6 +23,15 @@
 
 namespace latinime {
 
+// Naming convention
+// - Distance: "Weighted" edit distance -- used both for spatial and language.
+// - Compound Distance: Spatial Distance + Language Distance -- used for pruning and scoring
+// - Cost: delta/diff for Distance -- used both for spatial and language
+// - Length: "Non-weighted" -- used only for spatial
+// - Probability: "Non-weighted" -- used only for language
+// - Score: Final calibrated score based on the compound distance, which is sent to java as the
+//       priority of a suggested word
+
 class DicNode;
 class DicTraverseSession;
 class ProximityInfo;
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 923ab2e..966919e 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -19,6 +19,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Locale;
+
 @SmallTest
 public class StringUtilsTests extends AndroidTestCase {
     public void testContainsInArray() {
@@ -90,4 +92,48 @@
         assertEquals("in 5 elements at position 2,4", "key1,key3,key5",
                 StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
     }
+
+    public void testToTitleCase() {
+        assertEquals("SSaa",
+                StringUtils.toTitleCase("ßaa", Locale.GERMAN));
+        assertEquals("Aßa",
+                StringUtils.toTitleCase("aßa", Locale.GERMAN));
+        assertEquals("Iab",
+                StringUtils.toTitleCase("iab", Locale.ENGLISH));
+        assertEquals("Camelcase",
+                StringUtils.toTitleCase("cAmElCaSe", Locale.ENGLISH));
+        assertEquals("İab",
+                StringUtils.toTitleCase("iab", new Locale("tr")));
+        assertEquals("Aib",
+                StringUtils.toTitleCase("AİB", new Locale("tr")));
+        // For one character, toTitleCase returns the string as is. Not sure what the motivation
+        // is, but that's how it works now.
+        assertEquals("a",
+                StringUtils.toTitleCase("a", Locale.ENGLISH));
+        assertEquals("A",
+                StringUtils.toTitleCase("A", Locale.ENGLISH));
+    }
+
+    public void testGetCapitalizationType() {
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("capitalize"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("cApITalize"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("capitalizE"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("__c a piu$@tali56ze"));
+        assertEquals(StringUtils.CAPITALIZE_FIRST,
+                StringUtils.getCapitalizationType("A__c a piu$@tali56ze"));
+        assertEquals(StringUtils.CAPITALIZE_FIRST,
+                StringUtils.getCapitalizationType("Capitalize"));
+        assertEquals(StringUtils.CAPITALIZE_FIRST,
+                StringUtils.getCapitalizationType("     Capitalize"));
+        assertEquals(StringUtils.CAPITALIZE_ALL,
+                StringUtils.getCapitalizationType("CAPITALIZE"));
+        assertEquals(StringUtils.CAPITALIZE_ALL,
+                StringUtils.getCapitalizationType("  PI26LIE"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType(""));
+    }
 }