Merge "Add a utility method to StringUtils."
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 3a28699..54cff37 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -162,13 +162,11 @@
     // Key preview
     private static final boolean FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED = false;
     private final int mKeyPreviewLayoutId;
-    private final int mKeyPreviewOffset;
-    private final int mKeyPreviewHeight;
     // Free {@link TextView} pool that can be used for key preview.
     private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque();
     // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
     private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = CollectionUtils.newHashMap();
-    private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
+    private final KeyPreviewDrawParams mKeyPreviewDrawParams;
     private boolean mShowKeyPreviewPopup = true;
     private int mKeyPreviewLingerTimeout;
     private int mKeyPreviewZoomInDuration;
@@ -267,10 +265,7 @@
         final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
 
-        mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
-                R.styleable.MainKeyboardView_keyPreviewOffset, 0);
-        mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
-                R.styleable.MainKeyboardView_keyPreviewHeight, 0);
+        mKeyPreviewDrawParams = new KeyPreviewDrawParams(mainKeyboardViewAttr);
         mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt(
                 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
         mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId(
@@ -564,7 +559,7 @@
         final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
         final Keyboard keyboard = getKeyboard();
         if (!mShowKeyPreviewPopup) {
-            previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
+            previewParams.setVisibleOffset(-keyboard.mVerticalGap);
             return;
         }
 
@@ -591,17 +586,8 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
         final int keyDrawWidth = key.getDrawWidth();
         final int previewWidth = previewTextView.getMeasuredWidth();
-        final int previewHeight = mKeyPreviewHeight;
-        // The width and height of visible part of the key preview background. The content marker
-        // of the background 9-patch have to cover the visible part of the background.
-        previewParams.mPreviewVisibleWidth = previewWidth - previewTextView.getPaddingLeft()
-                - previewTextView.getPaddingRight();
-        previewParams.mPreviewVisibleHeight = previewHeight - previewTextView.getPaddingTop()
-                - previewTextView.getPaddingBottom();
-        // The distance between the top edge of the parent key and the bottom of the visible part
-        // of the key preview background.
-        previewParams.mPreviewVisibleOffset =
-                mKeyPreviewOffset - previewTextView.getPaddingBottom();
+        final int previewHeight = previewParams.mKeyPreviewHeight;
+        previewParams.setGeometry(previewTextView);
         getLocationInWindow(mOriginCoords);
         // The key preview is horizontally aligned with the center of the visible part of the
         // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
@@ -620,7 +606,7 @@
         }
         // The key preview is placed vertically above the top edge of the parent key with an
         // arbitrary offset.
-        final int previewY = key.getY() - previewHeight + mKeyPreviewOffset
+        final int previewY = key.getY() - previewHeight + previewParams.mKeyPreviewOffset
                 + CoordinateUtils.y(mOriginCoords);
 
         if (background != null) {
@@ -914,7 +900,7 @@
         // aligned with the bottom edge of the visible part of the key preview.
         // {@code mPreviewVisibleOffset} has been set appropriately in
         // {@link KeyboardView#showKeyPreview(PointerTracker)}.
-        final int pointY = key.getY() + mKeyPreviewDrawParams.mPreviewVisibleOffset;
+        final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset();
         moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
         tracker.onShowMoreKeysPanel(moreKeysPanel);
         // TODO: Implement zoom in animation of more keys panel.
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 6705243..2bff107 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -285,7 +285,7 @@
             // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
             final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
                     && !parentKey.noKeyPreview() && moreKeys.length == 1
-                    && keyPreviewDrawParams.mPreviewVisibleWidth > 0;
+                    && keyPreviewDrawParams.getVisibleWidth() > 0;
             if (singleMoreKeyWithPreview) {
                 // Use pre-computed width and height if this more keys keyboard has only one key to
                 // mitigate visual flicker between key preview and more keys keyboard.
@@ -294,8 +294,8 @@
                 // left/right/top paddings. The bottom paddings of both backgrounds don't need to
                 // be considered because the vertical positions of both backgrounds were already
                 // adjusted with their bottom paddings deducted.
-                width = keyPreviewDrawParams.mPreviewVisibleWidth;
-                height = keyPreviewDrawParams.mPreviewVisibleHeight + mParams.mVerticalGap;
+                width = keyPreviewDrawParams.getVisibleWidth();
+                height = keyPreviewDrawParams.getVisibleHeight() + mParams.mVerticalGap;
             } else {
                 final float padding = context.getResources().getDimension(
                         R.dimen.config_more_keys_keyboard_key_horizontal_padding)
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
index 609d1a5..d7518ee 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
@@ -16,7 +16,16 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.content.res.TypedArray;
+import android.view.View;
+
+import com.android.inputmethod.latin.R;
+
 public final class KeyPreviewDrawParams {
+    // XML attributes of {@link MainKeyboardView}.
+    public final int mKeyPreviewOffset;
+    public final int mKeyPreviewHeight;
+
     // The graphical geometry of the key preview.
     // <-width->
     // +-------+   ^
@@ -34,11 +43,48 @@
     // paddings. To align the more keys keyboard panel's visible part with the visible part of
     // the background, we need to record the width and height of key preview that don't include
     // invisible paddings.
-    public int mPreviewVisibleWidth;
-    public int mPreviewVisibleHeight;
+    private int mVisibleWidth;
+    private int mVisibleHeight;
     // The key preview may have an arbitrary offset and its background that may have a bottom
     // padding. To align the more keys keyboard and the key preview we also need to record the
     // offset between the top edge of parent key and the bottom of the visible part of key
     // preview background.
-    public int mPreviewVisibleOffset;
+    private int mVisibleOffset;
+
+    public KeyPreviewDrawParams(final TypedArray mainKeyboardViewAttr) {
+        mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
+                R.styleable.MainKeyboardView_keyPreviewOffset, 0);
+        mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
+                R.styleable.MainKeyboardView_keyPreviewHeight, 0);
+    }
+
+    public void setVisibleOffset(final int previewVisibleOffset) {
+        mVisibleOffset = previewVisibleOffset;
+    }
+
+    public int getVisibleOffset() {
+        return mVisibleOffset;
+    }
+
+    public void setGeometry(final View previewTextView) {
+        final int previewWidth = previewTextView.getMeasuredWidth();
+        final int previewHeight = mKeyPreviewHeight;
+        // The width and height of visible part of the key preview background. The content marker
+        // of the background 9-patch have to cover the visible part of the background.
+        mVisibleWidth = previewWidth - previewTextView.getPaddingLeft()
+                - previewTextView.getPaddingRight();
+        mVisibleHeight = previewHeight - previewTextView.getPaddingTop()
+                - previewTextView.getPaddingBottom();
+        // The distance between the top edge of the parent key and the bottom of the visible part
+        // of the key preview background.
+        setVisibleOffset(mKeyPreviewOffset - previewTextView.getPaddingBottom());
+    }
+
+    public int getVisibleWidth() {
+        return mVisibleWidth;
+    }
+
+    public int getVisibleHeight() {
+        return mVisibleHeight;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index c3bcf37..47bc6b0 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin;
 
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.utils.ResizableIntArray;
@@ -160,15 +161,21 @@
 
     private boolean isValidTimeStamps() {
         final int[] times = mTimes.getPrimitiveArray();
+        final int[] pointerIds = mPointerIds.getPrimitiveArray();
+        final SparseIntArray lastTimeOfPointers = new SparseIntArray();
         final int size = getPointerSize();
-        for (int i = 1; i < size; ++i) {
-            if (times[i] < times[i - 1]) {
+        for (int i = 0; i < size; ++i) {
+            final int pointerId = pointerIds[i];
+            final int time = times[i];
+            final int lastTime = lastTimeOfPointers.get(pointerId, time);
+            if (time < lastTime) {
                 // dump
                 for (int j = 0; j < size; ++j) {
                     Log.d(TAG, "--- (" + j + ") " + times[j]);
                 }
                 return false;
             }
+            lastTimeOfPointers.put(pointerId, time);
         }
         return true;
     }
diff --git a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp b/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp
index b810637..e37811b 100644
--- a/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/suggestions_output_utils.cpp
@@ -78,7 +78,8 @@
         outputAutoCommitFirstWordConfidence[0] =
                 computeFirstWordConfidence(&terminals[0]);
     }
-
+    const bool boostExactMatches = traverseSession->getDictionaryStructurePolicy()->
+            getHeaderStructurePolicy()->shouldBoostExactMatches();
     // Output suggestion results here
     for (int terminalIndex = 0; terminalIndex < terminalSize && outputWordIndex < MAX_RESULTS;
             ++terminalIndex) {
@@ -102,7 +103,7 @@
                 && !(isPossiblyOffensiveWord && isFirstCharUppercase);
         const int outputTypeFlags =
                 (isPossiblyOffensiveWord ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0)
-                | (isSafeExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
+                | ((isSafeExactMatch && boostExactMatches) ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
 
         // Entries that are blacklisted or do not represent a word should not be output.
         const bool isValidWord = !terminalDicNode->isBlacklistedOrNotAWord();
@@ -113,7 +114,8 @@
                 compoundDistance, traverseSession->getInputSize(),
                 terminalDicNode->getContainedErrorTypes(),
                 (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
-                         || (isValidWord && scoringPolicy->doesAutoCorrectValidWord()));
+                         || (isValidWord && scoringPolicy->doesAutoCorrectValidWord()),
+                boostExactMatches);
         if (maxScore < finalScore && isValidWord) {
             maxScore = finalScore;
         }
@@ -147,7 +149,7 @@
                      scoringPolicy->calculateFinalScore(compoundDistance,
                              traverseSession->getInputSize(),
                              terminalDicNode->getContainedErrorTypes(),
-                             true /* forceCommit */) : finalScore;
+                             true /* forceCommit */, boostExactMatches) : finalScore;
             const int updatedOutputWordIndex = outputShortcuts(&shortcutIt,
                     outputWordIndex, shortcutBaseScore, outputCodePoints, frequencies, outputTypes,
                     sameAsTyped);
diff --git a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
index b76b139..417620e 100644
--- a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
@@ -40,6 +40,8 @@
     virtual void readHeaderValueOrQuestionMark(const char *const key, int *outValue,
             int outValueSize) const = 0;
 
+    virtual bool shouldBoostExactMatches() const = 0;
+
  protected:
     DictionaryHeaderStructurePolicy() {}
 
diff --git a/native/jni/src/suggest/core/policy/scoring.h b/native/jni/src/suggest/core/policy/scoring.h
index 7833834..e581a97 100644
--- a/native/jni/src/suggest/core/policy/scoring.h
+++ b/native/jni/src/suggest/core/policy/scoring.h
@@ -28,7 +28,8 @@
 class Scoring {
  public:
     virtual int calculateFinalScore(const float compoundDistance, const int inputSize,
-            const ErrorTypeUtils::ErrorType containedErrorTypes, const bool forceCommit) const = 0;
+            const ErrorTypeUtils::ErrorType containedErrorTypes, const bool forceCommit,
+            const bool boostExactMatches) const = 0;
     virtual bool getMostProbableString(const DicTraverseSession *const traverseSession,
             const int terminalSize, const float languageWeight, int *const outputCodePoints,
             int *const type, int *const freq) const = 0;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
index a44f9f0..1320c65 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -146,6 +146,11 @@
         return mHasHistoricalInfoOfWords;
     }
 
+    AK_FORCE_INLINE bool shouldBoostExactMatches() const {
+        // TODO: Investigate better ways to handle exact matches for personalized dictionaries.
+        return !isDecayingDict();
+    }
+
     void readHeaderValueOrQuestionMark(const char *const key,
             int *outValue, int outValueSize) const;
 
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
index c777e72..8b405e8 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
@@ -50,14 +50,14 @@
 
     AK_FORCE_INLINE int calculateFinalScore(const float compoundDistance,
             const int inputSize, const ErrorTypeUtils::ErrorType containedErrorTypes,
-            const bool forceCommit) const {
+            const bool forceCommit, const bool boostExactMatches) const {
         const float maxDistance = ScoringParams::DISTANCE_WEIGHT_LANGUAGE
                 + static_cast<float>(inputSize) * ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT;
         float score = ScoringParams::TYPING_BASE_OUTPUT_SCORE - compoundDistance / maxDistance;
         if (forceCommit) {
             score += ScoringParams::AUTOCORRECT_OUTPUT_THRESHOLD;
         }
-        if (ErrorTypeUtils::isExactMatch(containedErrorTypes)) {
+        if (boostExactMatches && ErrorTypeUtils::isExactMatch(containedErrorTypes)) {
             score += ScoringParams::EXACT_MATCH_PROMOTION;
             if ((ErrorTypeUtils::MATCH_WITH_CASE_ERROR & containedErrorTypes) != 0) {
                 score -= ScoringParams::CASE_ERROR_PENALTY_FOR_EXACT_MATCH;
diff --git a/tools/dicttool/compat/android/util/SparseIntArray.java b/tools/dicttool/compat/android/util/SparseIntArray.java
new file mode 100644
index 0000000..ac8a04c
--- /dev/null
+++ b/tools/dicttool/compat/android/util/SparseIntArray.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 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 android.util;
+
+public class SparseIntArray {
+    private final SparseArray<Integer> mArray;
+
+    public SparseIntArray() {
+        this(10);
+    }
+
+    public SparseIntArray(final int initialCapacity) {
+        mArray = new SparseArray<Integer>(initialCapacity);
+    }
+
+    public int size() {
+        return mArray.size();
+    }
+
+    public void clear() {
+        mArray.clear();
+    }
+
+    public void put(final int key, final int value) {
+        mArray.put(key, value);
+    }
+
+    public int get(final int key) {
+        return get(key, 0);
+    }
+
+    public int get(final int key, final int valueIfKeyNotFound) {
+        return mArray.get(key, valueIfKeyNotFound);
+    }
+
+    public int indexOfKey(final int key) {
+        return mArray.indexOfKey(key);
+    }
+
+    public int keyAt(final int index) {
+        return mArray.keyAt(index);
+    }
+}