Merge "Add a new production flag for Cursor/Anchor monitor"
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 1bef3c2..6d8f787 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -40,7 +40,7 @@
     eo: Esperanto/spanish
     es: Spanish/spanish
     es_US: Spanish (United States)/spanish
-    (es_419: Spanish (Latin America)/qwerty)
+    es_419: Spanish (Latin America)/spanish
     et_EE: Estonian (Estonia)/nordic
     eu_ES: Basque (Spain)/spanish
     fa: Persian/farsi
@@ -248,16 +248,14 @@
             android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable,EmojiCapable"
             android:isAsciiCapable="true"
     />
-    <!--
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
-            android:subtypeId="0x623f9286"
+            android:subtypeId="0xa23e5d19"
             android:imeSubtypeLocale="es_419"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable,EmojiCapable"
             android:isAsciiCapable="true"
     />
-    -->
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xec2d3955"
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 7b37777..5e36d97 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -247,7 +247,9 @@
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
             final int sessionId, final float[] inOutLanguageWeight) {
-        if (!isValidDictionary()) return null;
+        if (!isValidDictionary()) {
+            return null;
+        }
 
         Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
         // TODO: toLowerCase in the native code
@@ -257,12 +259,11 @@
         final boolean isGesture = composer.isBatchMode();
         final int inputSize;
         if (!isGesture) {
-            final int composerSize = composer.sizeWithoutTrailingSingleQuotes();
-            if (composerSize > MAX_WORD_LENGTH - 1) return null;
-            for (int i = 0; i < composerSize; i++) {
-                mInputCodePoints[i] = composer.getCodeAt(i);
+            inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
+                    mInputCodePoints, MAX_WORD_LENGTH);
+            if (inputSize < 0) {
+                return null;
             }
-            inputSize = composerSize;
         } else {
             inputSize = inputPointers.getPointerSize();
         }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 81d642f..02f18cd 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -131,29 +131,42 @@
         return mCodePointSize;
     }
 
+    /**
+     * Copy the code points in the typed word to a destination array of ints.
+     *
+     * If the array is too small to hold the code points in the typed word, nothing is copied and
+     * -1 is returned.
+     *
+     * @param destination the array of ints.
+     * @param maxSize the size of the array.
+     * @return the number of copied code points.
+     */
+    public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
+            final int[] destination, final int maxSize) {
+        int i = mTypedWordCache.length() - 1;
+        while (i >= 0 && mTypedWordCache.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
+            --i;
+        }
+        if (i < 0) {
+            // The string is empty or contains only single quotes.
+            return 0;
+        }
+        final int codePointSize = Character.codePointCount(mTypedWordCache, 0, i);
+        if (codePointSize > maxSize) {
+            return -1;
+        }
+        return StringUtils.copyCodePointsAndReturnCodePointCount(destination, mTypedWordCache, 0,
+                i + 1, true /* downCase */);
+    }
+
     public boolean isSingleLetter() {
         return size() == 1;
     }
 
-    // When the composition contains trailing quotes, we don't pass them to the suggestion engine.
-    // This is because "'tgis'" should be corrected to "'this'", but we can't afford to consider
-    // single quotes as separators because of their very common use as apostrophes.
-    public int sizeWithoutTrailingSingleQuotes() {
-        return size() - mTrailingSingleQuotesCount;
-    }
-
     public final boolean isComposingWord() {
         return size() > 0;
     }
 
-    // TODO: make sure that the index should not exceed MAX_WORD_LENGTH
-    public int getCodeAt(int index) {
-        if (index >= MAX_WORD_LENGTH) {
-            return -1;
-        }
-        return mPrimaryKeyCodes[index];
-    }
-
     public InputPointers getInputPointers() {
         return mInputPointers;
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index accbc8b..374badc 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -191,13 +191,42 @@
         }
         final int[] codePoints =
                 new int[Character.codePointCount(charSequence, startIndex, endIndex)];
+        copyCodePointsAndReturnCodePointCount(codePoints, charSequence, startIndex, endIndex,
+                false /* downCase */);
+        return codePoints;
+    }
+
+    /**
+     * Copies the codepoints in a CharSequence to an int array.
+     *
+     * This method assumes there is enough space in the array to store the code points. The size
+     * can be measured with Character#codePointCount(CharSequence, int, int) before passing to this
+     * method. If the int array is too small, an ArrayIndexOutOfBoundsException will be thrown.
+     * Also, this method makes no effort to be thread-safe. Do not modify the CharSequence while
+     * this method is running, or the behavior is undefined.
+     * This method can optionally downcase code points before copying them, but it pays no attention
+     * to locale while doing so.
+     *
+     * @param destination the int array.
+     * @param charSequence the CharSequence.
+     * @param startIndex the start index inside the string in java chars, inclusive.
+     * @param endIndex the end index inside the string in java chars, exclusive.
+     * @param downCase if this is true, code points will be downcased before being copied.
+     * @return the number of copied code points.
+     */
+    public static int copyCodePointsAndReturnCodePointCount(final int[] destination,
+            final CharSequence charSequence, final int startIndex, final int endIndex,
+            final boolean downCase) {
         int destIndex = 0;
         for (int index = startIndex; index < endIndex;
                 index = Character.offsetByCodePoints(charSequence, index, 1)) {
-            codePoints[destIndex] = Character.codePointAt(charSequence, index);
+            final int codePoint = Character.codePointAt(charSequence, index);
+            // TODO: stop using this, as it's not aware of the locale and does not always do
+            // the right thing.
+            destination[destIndex] = downCase ? Character.toLowerCase(codePoint) : codePoint;
             destIndex++;
         }
-        return codePoints;
+        return destIndex;
     }
 
     public static int[] toSortedCodePointArray(final String string) {
diff --git a/native/jni/src/suggest/core/layout/proximity_info_params.cpp b/native/jni/src/suggest/core/layout/proximity_info_params.cpp
index a70dd7e..68bb0ae 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_params.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_params.cpp
@@ -24,9 +24,6 @@
 const float ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G = 0.5f;
 
 /* Per method constants */
-// Used by ProximityInfoStateUtils::initGeometricDistanceInfos()
-const float ProximityInfoParams::NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD = 4.0f;
-
 // Used by ProximityInfoStateUtils::updateNearKeysDistances()
 const float ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_DISTANCE = 2.0f;
 
@@ -50,7 +47,7 @@
 const int ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE = 4;
 
 // Used by ProximityInfoStateUtils::updateAlignPointProbabilities()
-const float ProximityInfoParams::MIN_PROBABILITY = 0.000001f;
+const float ProximityInfoParams::MIN_PROBABILITY = 0.000005f;
 const float ProximityInfoParams::MAX_SKIP_PROBABILITY = 0.95f;
 const float ProximityInfoParams::SKIP_FIRST_POINT_PROBABILITY = 0.01f;
 const float ProximityInfoParams::SKIP_LAST_POINT_PROBABILITY = 0.1f;
diff --git a/native/jni/src/suggest/core/layout/proximity_info_params.h b/native/jni/src/suggest/core/layout/proximity_info_params.h
index b8e9f5d..d9515c8 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_params.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_params.h
@@ -28,9 +28,6 @@
     static const float VERTICAL_SWEET_SPOT_SCALE;
     static const float VERTICAL_SWEET_SPOT_SCALE_G;
 
-    // Used by ProximityInfoStateUtils::initGeometricDistanceInfos()
-    static const float NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD;
-
     // Used by ProximityInfoStateUtils::updateNearKeysDistances()
     static const float NEAR_KEY_THRESHOLD_FOR_DISTANCE;
 
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
index b75c2ef..e585f90 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -91,7 +91,6 @@
         mSampledInputIndice.clear();
         mSampledLengthCache.clear();
         mSampledNormalizedSquaredLengthCache.clear();
-        mSampledNearKeySets.clear();
         mSampledSearchKeySets.clear();
         mSpeedRates.clear();
         mBeelineSpeedPercentiles.clear();
@@ -126,18 +125,17 @@
     if (mSampledInputSize > 0) {
         ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize,
                 lastSavedInputSize, isGeometric, &mSampledInputXs, &mSampledInputYs,
-                &mSampledNearKeySets, &mSampledNormalizedSquaredLengthCache);
+                &mSampledNormalizedSquaredLengthCache);
         if (isGeometric) {
             // updates probabilities of skipping or mapping each key for all points.
             ProximityInfoStateUtils::updateAlignPointProbabilities(
                     mMaxPointToKeyLength, mProximityInfo->getMostCommonKeyWidth(),
                     mProximityInfo->getKeyCount(), lastSavedInputSize, mSampledInputSize,
                     &mSampledInputXs, &mSampledInputYs, &mSpeedRates, &mSampledLengthCache,
-                    &mSampledNormalizedSquaredLengthCache, &mSampledNearKeySets,
-                    mProximityInfo, &mCharProbabilities);
+                    &mSampledNormalizedSquaredLengthCache, mProximityInfo, &mCharProbabilities);
             ProximityInfoStateUtils::updateSampledSearchKeySets(mProximityInfo,
                     mSampledInputSize, lastSavedInputSize, &mSampledLengthCache,
-                    &mSampledNearKeySets, &mSampledSearchKeySets,
+                    &mCharProbabilities, &mSampledSearchKeySets,
                     &mSampledSearchKeyVectors);
             mMostProbableStringProbability = ProximityInfoStateUtils::getMostProbableString(
                     mProximityInfo, mSampledInputSize, &mCharProbabilities, mMostProbableString);
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
index e253d95..d66121b 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -50,9 +50,9 @@
               mSampledInputXs(), mSampledInputYs(), mSampledTimes(), mSampledInputIndice(),
               mSampledLengthCache(), mBeelineSpeedPercentiles(),
               mSampledNormalizedSquaredLengthCache(), mSpeedRates(), mDirections(),
-              mCharProbabilities(), mSampledNearKeySets(), mSampledSearchKeySets(),
-              mSampledSearchKeyVectors(), mTouchPositionCorrectionEnabled(false),
-              mSampledInputSize(0), mMostProbableStringProbability(0.0f) {
+              mCharProbabilities(), mSampledSearchKeySets(), mSampledSearchKeyVectors(),
+              mTouchPositionCorrectionEnabled(false), mSampledInputSize(0),
+              mMostProbableStringProbability(0.0f) {
         memset(mInputProximities, 0, sizeof(mInputProximities));
         memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
         memset(mMostProbableString, 0, sizeof(mMostProbableString));
@@ -216,10 +216,6 @@
     std::vector<float> mDirections;
     // 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
-    // 1. Used to calculate the probability of the key
-    // 2. Used to calculate mSampledSearchKeySets
-    std::vector<ProximityInfoStateUtils::NearKeycodesSet> mSampledNearKeySets;
     // The vector for the key code set which holds nearby keys of some trailing sampled input points
     // for each sampled input point. These nearby keys contain the next characters which can be in
     // the dictionary. Specifically, currently we are looking for keys nearby trailing sampled
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
index 638297e..72bb68f 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
@@ -188,13 +188,10 @@
         const int lastSavedInputSize, const bool isGeometric,
         const std::vector<int> *const sampledInputXs,
         const std::vector<int> *const sampledInputYs,
-        std::vector<NearKeycodesSet> *sampledNearKeySets,
         std::vector<float> *sampledNormalizedSquaredLengthCache) {
-    sampledNearKeySets->resize(sampledInputSize);
     const int keyCount = proximityInfo->getKeyCount();
     sampledNormalizedSquaredLengthCache->resize(sampledInputSize * keyCount);
     for (int i = lastSavedInputSize; i < sampledInputSize; ++i) {
-        (*sampledNearKeySets)[i].reset();
         for (int k = 0; k < keyCount; ++k) {
             const int index = i * keyCount + k;
             const int x = (*sampledInputXs)[i];
@@ -203,10 +200,6 @@
                     proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(
                             k, x, y, isGeometric);
             (*sampledNormalizedSquaredLengthCache)[index] = normalizedSquaredDistance;
-            if (normalizedSquaredDistance
-                    < ProximityInfoParams::NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD) {
-                (*sampledNearKeySets)[i][k] = true;
-            }
         }
     }
 }
@@ -626,7 +619,6 @@
         const std::vector<float> *const sampledSpeedRates,
         const std::vector<int> *const sampledLengthCache,
         const std::vector<float> *const sampledNormalizedSquaredLengthCache,
-        std::vector<NearKeycodesSet> *sampledNearKeySets,
         const ProximityInfo *const proximityInfo,
         std::vector<hash_map_compat<int, float> > *charProbabilities) {
     charProbabilities->resize(sampledInputSize);
@@ -643,12 +635,10 @@
 
         float nearestKeyDistance = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
         for (int j = 0; j < keyCount; ++j) {
-            if ((*sampledNearKeySets)[i].test(j)) {
-                const float distance = getPointToKeyByIdLength(
-                        maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount, i, j);
-                if (distance < nearestKeyDistance) {
-                    nearestKeyDistance = distance;
-                }
+            const float distance = getPointToKeyByIdLength(
+                    maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount, i, j);
+            if (distance < nearestKeyDistance) {
+                nearestKeyDistance = distance;
             }
         }
 
@@ -744,27 +734,23 @@
         // Summing up probability densities of all near keys.
         float sumOfProbabilityDensities = 0.0f;
         for (int j = 0; j < keyCount; ++j) {
-            if ((*sampledNearKeySets)[i].test(j)) {
-                sumOfProbabilityDensities += distribution.getProbabilityDensity(
-                        proximityInfo->getKeyCenterXOfKeyIdG(j,
-                                NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
-                        proximityInfo->getKeyCenterYOfKeyIdG(j,
-                                NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
-            }
+            sumOfProbabilityDensities += distribution.getProbabilityDensity(
+                    proximityInfo->getKeyCenterXOfKeyIdG(j,
+                            NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
+                    proximityInfo->getKeyCenterYOfKeyIdG(j,
+                            NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
         }
 
         // Split the probability of an input point to keys that are close to the input point.
         for (int j = 0; j < keyCount; ++j) {
-            if ((*sampledNearKeySets)[i].test(j)) {
-                const float probabilityDensity = distribution.getProbabilityDensity(
-                        proximityInfo->getKeyCenterXOfKeyIdG(j,
-                                NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
-                        proximityInfo->getKeyCenterYOfKeyIdG(j,
-                                NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
-                const float probability = inputCharProbability * probabilityDensity
-                        / sumOfProbabilityDensities;
-                (*charProbabilities)[i][j] = probability;
-            }
+            const float probabilityDensity = distribution.getProbabilityDensity(
+                    proximityInfo->getKeyCenterXOfKeyIdG(j,
+                            NOT_A_COORDINATE /* referencePointX */, true /* isGeometric */),
+                    proximityInfo->getKeyCenterYOfKeyIdG(j,
+                            NOT_A_COORDINATE /* referencePointY */, true /* isGeometric */));
+            const float probability = inputCharProbability * probabilityDensity
+                    / sumOfProbabilityDensities;
+            (*charProbabilities)[i][j] = probability;
         }
     }
 
@@ -820,10 +806,9 @@
         for (int j = 0; j < keyCount; ++j) {
             hash_map_compat<int, float>::iterator it = (*charProbabilities)[i].find(j);
             if (it == (*charProbabilities)[i].end()){
-                (*sampledNearKeySets)[i].reset(j);
+                continue;
             } else if(it->second < ProximityInfoParams::MIN_PROBABILITY) {
                 // Erases from near keys vector because it has very low probability.
-                (*sampledNearKeySets)[i].reset(j);
                 (*charProbabilities)[i].erase(j);
             } else {
                 it->second = -logf(it->second);
@@ -835,9 +820,8 @@
 
 /* static */ void ProximityInfoStateUtils::updateSampledSearchKeySets(
         const ProximityInfo *const proximityInfo, const int sampledInputSize,
-        const int lastSavedInputSize,
-        const std::vector<int> *const sampledLengthCache,
-        const std::vector<NearKeycodesSet> *const sampledNearKeySets,
+        const int lastSavedInputSize, const std::vector<int> *const sampledLengthCache,
+        const std::vector<hash_map_compat<int, float> > *const charProbabilities,
         std::vector<NearKeycodesSet> *sampledSearchKeySets,
         std::vector<std::vector<int> > *sampledSearchKeyVectors) {
     sampledSearchKeySets->resize(sampledInputSize);
@@ -854,7 +838,12 @@
             if ((*sampledLengthCache)[j] - (*sampledLengthCache)[i] >= readForwordLength) {
                 break;
             }
-            (*sampledSearchKeySets)[i] |= (*sampledNearKeySets)[j];
+            for(const auto& charProbability : charProbabilities->at(j)) {
+                if (charProbability.first == NOT_AN_INDEX) {
+                    continue;
+                }
+                (*sampledSearchKeySets)[i].set(charProbability.first);
+            }
         }
     }
     const int keyCount = proximityInfo->getKeyCount();
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.h b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
index 5d7a9c5..7aa20c3 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
@@ -71,13 +71,12 @@
             const std::vector<float> *const sampledSpeedRates,
             const std::vector<int> *const sampledLengthCache,
             const std::vector<float> *const sampledNormalizedSquaredLengthCache,
-            std::vector<NearKeycodesSet> *sampledNearKeySets,
             const ProximityInfo *const proximityInfo,
             std::vector<hash_map_compat<int, float> > *charProbabilities);
     static void updateSampledSearchKeySets(const ProximityInfo *const proximityInfo,
             const int sampledInputSize, const int lastSavedInputSize,
             const std::vector<int> *const sampledLengthCache,
-            const std::vector<NearKeycodesSet> *const sampledNearKeySets,
+            const std::vector<hash_map_compat<int, float> > *const charProbabilities,
             std::vector<NearKeycodesSet> *sampledSearchKeySets,
             std::vector<std::vector<int> > *sampledSearchKeyVectors);
     static float getPointToKeyByIdLength(const float maxPointToKeyLength,
@@ -87,7 +86,6 @@
             const int sampledInputSize, const int lastSavedInputSize, const bool isGeometric,
             const std::vector<int> *const sampledInputXs,
             const std::vector<int> *const sampledInputYs,
-            std::vector<NearKeycodesSet> *sampledNearKeySets,
             std::vector<float> *sampledNormalizedSquaredLengthCache);
     static void initPrimaryInputWord(const int inputSize, const int *const inputProximities,
             int *primaryInputWord);
diff --git a/native/jni/src/suggest/core/policy/traversal.h b/native/jni/src/suggest/core/policy/traversal.h
index d3b8da0..8ddaa05 100644
--- a/native/jni/src/suggest/core/policy/traversal.h
+++ b/native/jni/src/suggest/core/policy/traversal.h
@@ -45,6 +45,7 @@
     virtual float getMaxSpatialDistance() const = 0;
     virtual int getDefaultExpandDicNodeSize() const = 0;
     virtual int getMaxCacheSize(const int inputSize) const = 0;
+    virtual int getTerminalCacheSize() const = 0;
     virtual bool isPossibleOmissionChildNode(const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
     virtual bool isGoodToTraverseNextWord(const DicNode *const dicNode) const = 0;
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 433820a..e675e0b 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -88,7 +88,7 @@
     } else {
         // Restart recognition at the root.
         traverseSession->resetCache(TRAVERSAL->getMaxCacheSize(traverseSession->getInputSize()),
-                MAX_RESULTS);
+                TRAVERSAL->getTerminalCacheSize());
         // Create a new dic node here
         DicNode rootNode;
         DicNodeUtils::initAsRoot(traverseSession->getDictionaryStructurePolicy(),
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index 5ba8bfa..cb3dfac 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -146,6 +146,10 @@
                 : ScoringParams::MAX_CACHE_DIC_NODE_SIZE;
     }
 
+    AK_FORCE_INLINE int getTerminalCacheSize() const {
+        return MAX_RESULTS;
+    }
+
     AK_FORCE_INLINE bool isPossibleOmissionChildNode(
             const DicTraverseSession *const traverseSession, const DicNode *const parentDicNode,
             const DicNode *const dicNode) const {
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
index 57d2950..18390b2 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
@@ -25,8 +25,8 @@
 
 @SmallTest
 public class KeyboardLayoutSetSubtypesCountTests extends KeyboardLayoutSetTestsBase {
-    private static final int NUMBER_OF_SUBTYPES = 68;
-    private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 43;
+    private static final int NUMBER_OF_SUBTYPES = 69;
+    private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 44;
     private static final int NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES = 2;
 
     private static String toString(final ArrayList<InputMethodSubtype> subtypeList) {
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish419.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish419.java
new file mode 100644
index 0000000..75aad13
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsSpanish419.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 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.keyboard.layout.tests;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.inputmethod.keyboard.layout.LayoutBase;
+import com.android.inputmethod.keyboard.layout.Spanish;
+
+import java.util.Locale;
+
+/**
+ * es_419: Spanish (Latin America)/spanish
+ */
+@SmallTest
+public class TestsSpanish419 extends LayoutTestsBase {
+    private static final Locale LOCALE = new Locale("es", "419");
+    private static final LayoutBase LAYOUT = new Spanish(new SpanishCustomizer(LOCALE));
+
+    @Override
+    LayoutBase getLayout() { return LAYOUT; }
+}
diff --git a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
index e55c32b..2a4ead3 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
@@ -308,4 +308,68 @@
             assertEquals(objs[i], newObjArray.get(i));
         }
     }
+
+    public void testToCodePointArray() {
+        final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh\u0000\u2002\u2003\u3000xx";
+        final int[] EXPECTED_RESULT = new int[] { 'a', 'b', 'c', 'd', 'e', 0x286D7, 'f', 'g', 'h',
+                0, 0x2002, 0x2003, 0x3000, 'x', 'x'};
+        final int[] codePointArray = StringUtils.toCodePointArray(STR_WITH_SUPPLEMENTARY_CHAR, 0,
+                STR_WITH_SUPPLEMENTARY_CHAR.length());
+        assertEquals("toCodePointArray, size matches", codePointArray.length,
+                EXPECTED_RESULT.length);
+        for (int i = 0; i < EXPECTED_RESULT.length; ++i) {
+            assertEquals("toCodePointArray position " + i, codePointArray[i], EXPECTED_RESULT[i]);
+        }
+    }
+
+    public void testCopyCodePointsAndReturnCodePointCount() {
+        final String STR_WITH_SUPPLEMENTARY_CHAR = "AbcDE\uD861\uDED7fGh\u0000\u2002\u3000あx";
+        final int[] EXPECTED_RESULT = new int[] { 'A', 'b', 'c', 'D', 'E', 0x286D7,
+                'f', 'G', 'h', 0, 0x2002, 0x3000, 'あ', 'x'};
+        final int[] EXPECTED_RESULT_DOWNCASE = new int[] { 'a', 'b', 'c', 'd', 'e', 0x286D7,
+                'f', 'g', 'h', 0, 0x2002, 0x3000, 'あ', 'x'};
+
+        int[] codePointArray = new int[50];
+        int codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray,
+                STR_WITH_SUPPLEMENTARY_CHAR, 0,
+                STR_WITH_SUPPLEMENTARY_CHAR.length(), false /* downCase */);
+        assertEquals("copyCodePointsAndReturnCodePointCount, size matches", codePointCount,
+                EXPECTED_RESULT.length);
+        for (int i = 0; i < codePointCount; ++i) {
+            assertEquals("copyCodePointsAndReturnCodePointCount position " + i, codePointArray[i],
+                    EXPECTED_RESULT[i]);
+        }
+
+        codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray,
+                STR_WITH_SUPPLEMENTARY_CHAR, 0,
+                STR_WITH_SUPPLEMENTARY_CHAR.length(), true /* downCase */);
+        assertEquals("copyCodePointsAndReturnCodePointCount downcase, size matches", codePointCount,
+                EXPECTED_RESULT_DOWNCASE.length);
+        for (int i = 0; i < codePointCount; ++i) {
+            assertEquals("copyCodePointsAndReturnCodePointCount position " + i, codePointArray[i],
+                    EXPECTED_RESULT_DOWNCASE[i]);
+        }
+
+        final int JAVA_CHAR_COUNT = 8;
+        final int CODEPOINT_COUNT = 7;
+        codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray,
+                STR_WITH_SUPPLEMENTARY_CHAR, 0, JAVA_CHAR_COUNT, false /* downCase */);
+        assertEquals("copyCodePointsAndReturnCodePointCount, size matches", codePointCount,
+                CODEPOINT_COUNT);
+        for (int i = 0; i < codePointCount; ++i) {
+            assertEquals("copyCodePointsAndReturnCodePointCount position " + i, codePointArray[i],
+                    EXPECTED_RESULT[i]);
+        }
+
+        boolean exceptionHappened = false;
+        codePointArray = new int[5];
+        try {
+            codePointCount = StringUtils.copyCodePointsAndReturnCodePointCount(codePointArray,
+                    STR_WITH_SUPPLEMENTARY_CHAR, 0, JAVA_CHAR_COUNT, false /* downCase */);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            exceptionHappened = true;
+        }
+        assertTrue("copyCodePointsAndReturnCodePointCount throws when array is too small",
+                exceptionHappened);
+    }
 }