Merge "Give PatriciaTrieReadingUtils methods for reading nodes."
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index bb1aa47..166be81 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -273,7 +273,7 @@
     public void startDoubleTapShiftKeyTimer() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
         if (keyboardView != null) {
-            keyboardView.getTimerProxy().startDoubleTapShiftKeyTimer();
+            keyboardView.startDoubleTapShiftKeyTimer();
         }
     }
 
@@ -282,7 +282,7 @@
     public void cancelDoubleTapShiftKeyTimer() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
         if (keyboardView != null) {
-            keyboardView.getTimerProxy().cancelDoubleTapShiftKeyTimer();
+            keyboardView.cancelDoubleTapShiftKeyTimer();
         }
     }
 
@@ -290,7 +290,7 @@
     @Override
     public boolean isInDoubleTapShiftKeyTimeout() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
-        return keyboardView != null && keyboardView.getTimerProxy().isInDoubleTapShiftKeyTimeout();
+        return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 43baf61..98eed26 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -193,8 +193,6 @@
         private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
         private static final int MSG_UPDATE_BATCH_INPUT = 4;
 
-        private final int mKeyRepeatStartTimeout;
-        private final int mKeyRepeatInterval;
         private final int mIgnoreAltCodeKeyTimeout;
         private final int mGestureRecognitionUpdateTime;
 
@@ -202,10 +200,6 @@
                 final TypedArray mainKeyboardViewAttr) {
             super(outerInstance);
 
-            mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
-            mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_keyRepeatInterval, 0);
             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
             mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
@@ -224,17 +218,7 @@
                 startWhileTypingFadeinAnimation(keyboardView);
                 break;
             case MSG_REPEAT_KEY:
-                final Key currentKey = tracker.getKey();
-                final int code = msg.arg1;
-                if (currentKey != null && currentKey.mCode == code) {
-                    startKeyRepeatTimer(tracker, mKeyRepeatInterval);
-                    startTypingStateTimer(currentKey);
-                    final KeyboardActionListener listener =
-                            keyboardView.getKeyboardActionListener();
-                    listener.onPressKey(code, true /* isRepeatKey */, true /* isSinglePointer */);
-                    listener.onCodeInput(code,
-                            Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-                }
+                tracker.onKeyRepeat(msg.arg1);
                 break;
             case MSG_LONGPRESS_KEY:
                 keyboardView.onLongPress(tracker);
@@ -246,19 +230,15 @@
             }
         }
 
-        private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
+        @Override
+        public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) {
             final Key key = tracker.getKey();
-            if (key == null) {
+            if (key == null || delay == 0) {
                 return;
             }
             sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
         }
 
-        @Override
-        public void startKeyRepeatTimer(final PointerTracker tracker) {
-            startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
-        }
-
         public void cancelKeyRepeatTimer() {
             removeMessages(MSG_REPEAT_KEY);
         }
@@ -1027,6 +1007,18 @@
         }
     }
 
+    public void startDoubleTapShiftKeyTimer() {
+        mKeyTimerHandler.startDoubleTapShiftKeyTimer();
+    }
+
+    public void cancelDoubleTapShiftKeyTimer() {
+        mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
+    }
+
+    public boolean isInDoubleTapShiftKeyTimeout() {
+        return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent event) {
         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 20fc109..7079b4d 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -94,7 +94,7 @@
     public interface TimerProxy {
         public void startTypingStateTimer(Key typedKey);
         public boolean isTypingState();
-        public void startKeyRepeatTimer(PointerTracker tracker);
+        public void startKeyRepeatTimer(PointerTracker tracker, int delay);
         public void startLongPressTimer(PointerTracker tracker, int delay);
         public void cancelLongPressTimer();
         public void startDoubleTapShiftKeyTimer();
@@ -111,7 +111,7 @@
             @Override
             public boolean isTypingState() { return false; }
             @Override
-            public void startKeyRepeatTimer(PointerTracker tracker) {}
+            public void startKeyRepeatTimer(PointerTracker tracker, int delay) {}
             @Override
             public void startLongPressTimer(PointerTracker tracker, int delay) {}
             @Override
@@ -138,6 +138,8 @@
         public final int mTouchNoiseThresholdTime;
         public final int mTouchNoiseThresholdDistance;
         public final int mSuppressKeyPreviewAfterBatchInputDuration;
+        public final int mKeyRepeatStartTimeout;
+        public final int mKeyRepeatInterval;
         public final int mLongPressShiftLockTimeout;
 
         public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
@@ -147,6 +149,8 @@
             mTouchNoiseThresholdTime = 0;
             mTouchNoiseThresholdDistance = 0;
             mSuppressKeyPreviewAfterBatchInputDuration = 0;
+            mKeyRepeatStartTimeout = 0;
+            mKeyRepeatInterval = 0;
             mLongPressShiftLockTimeout = 0;
         }
 
@@ -159,6 +163,10 @@
                     R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
             mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
+            mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
+            mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_keyRepeatInterval, 0);
             mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
         }
@@ -477,7 +485,8 @@
     }
 
     // Returns true if keyboard has been changed by this callback.
-    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
+    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
+            final boolean isRepeatKey) {
         // While gesture input is going on, this method should be a no-operation. But when gesture
         // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
         // are set to false. To keep this method is a no-operation,
@@ -487,17 +496,17 @@
         }
         final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
         if (DEBUG_LISTENER) {
-            Log.d(TAG, String.format("[%d] onPress    : %s%s%s", mPointerId,
+            Log.d(TAG, String.format("[%d] onPress    : %s%s%s%s", mPointerId,
                     KeyDetector.printableCode(key),
                     ignoreModifierKey ? " ignoreModifier" : "",
-                    key.isEnabled() ? "" : " disabled"));
+                    key.isEnabled() ? "" : " disabled",
+                    isRepeatKey ? " repeat" : ""));
         }
         if (ignoreModifierKey) {
             return false;
         }
         if (key.isEnabled()) {
-            mListener.onPressKey(key.mCode, false /* isRepeatKey */,
-                    getActivePointerTrackerCount() == 1);
+            mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
             mKeyboardLayoutHasBeenChanged = false;
             mTimerProxy.startTypingStateTimer(key);
@@ -917,7 +926,7 @@
         }
         // A gesture should start only from a non-modifier key.
         mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
-                && key != null && !key.isModifier();
+                && key != null && !key.isModifier() && !key.isRepeatable();
         if (mIsDetectingGesture) {
             if (getActivePointerTrackerCount() == 1) {
                 sGestureFirstDownTime = eventTime;
@@ -945,7 +954,7 @@
             // This onPress call may have changed keyboard layout. Those cases are detected at
             // {@link #setKeyboard}. In those cases, we should update key according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
                 key = onDownKey(x, y, eventTime);
             }
 
@@ -1035,7 +1044,7 @@
         // at {@link #setKeyboard}. In those cases, we should update key according
         // to the new keyboard layout.
         Key key = newKey;
-        if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+        if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
             key = onMoveKey(x, y);
         }
         onMoveToNewKey(key, x, y);
@@ -1304,16 +1313,6 @@
         }
     }
 
-    private void startRepeatKey(final Key key) {
-        if (sInGesture) return;
-        if (key == null) return;
-        if (!key.isRepeatable()) return;
-        // Don't start key repeat when we are in sliding input mode.
-        if (mIsInSlidingKeyInput) return;
-        detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
-        mTimerProxy.startKeyRepeatTimer(this);
-    }
-
     private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
             final Key newKey) {
         if (mKeyDetector == null) {
@@ -1394,6 +1393,26 @@
         callListenerOnRelease(key, code, false /* withSliding */);
     }
 
+    private void startRepeatKey(final Key key) {
+        if (sInGesture) return;
+        if (key == null) return;
+        if (!key.isRepeatable()) return;
+        // Don't start key repeat when we are in sliding input mode.
+        if (mIsInSlidingKeyInput) return;
+        detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
+        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout);
+    }
+
+    public void onKeyRepeat(final int code) {
+        final Key key = getKey();
+        if (key == null || key.mCode != code) {
+            return;
+        }
+        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval);
+        callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */);
+        callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
+    }
+
     private void printTouchEvent(final String title, final int x, final int y,
             final long eventTime) {
         final Key key = mKeyDetector.detectHitKey(x, y);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 614c143..719c7c8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -76,6 +76,7 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsActivity;
@@ -170,6 +171,7 @@
     private boolean mIsMainDictionaryAvailable;
     private UserBinaryDictionary mUserDictionary;
     private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
+    private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -560,6 +562,9 @@
         mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
                 .getUserHistoryPredictionDictionary(this, localeStr, prefs);
         newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+        mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper
+                .getPersonalizationPredictionDictionary(this, localeStr, prefs);
+        newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
 
         final Suggest oldSuggest = mSuggest;
         resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
@@ -2509,9 +2514,9 @@
         final SettingsValues currentSettings = mSettings.getCurrent();
         if (!currentSettings.mCorrectionEnabled) return null;
 
-        final UserHistoryPredictionDictionary userHistoryDictionary =
+        final UserHistoryPredictionDictionary userHistoryPredictionDictionary =
                 mUserHistoryPredictionDictionary;
-        if (userHistoryDictionary == null) return null;
+        if (userHistoryPredictionDictionary == null) return null;
 
         final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
         final String secondWord;
@@ -2525,7 +2530,7 @@
         final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
                 suggest.getUnigramDictionaries(), suggestion);
         if (maxFreq == 0) return null;
-        userHistoryDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
+        userHistoryPredictionDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
         return prevWord;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 6b01667..2879e2e 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.BoundedTreeSet;
@@ -174,6 +175,12 @@
                 userHistoryPredictionDictionary);
     }
 
+    public void setPersonalizationPredictionDictionary(
+            final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
+                personalizationPredictionDictionary);
+    }
+
     public void setAutoCorrectionThreshold(float threshold) {
         mAutoCorrectionThreshold = threshold;
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
index f5dae99..9f013df 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -30,25 +30,52 @@
     private static final boolean DEBUG = false;
 
     private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
-            sLangDictCache = CollectionUtils.newConcurrentHashMap();
+            sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
+
+    private static final ConcurrentHashMap<String,
+            SoftReference<PersonalizationPredictionDictionary>>
+                    sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
 
     public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
-        synchronized (sLangDictCache) {
-            if (sLangDictCache.containsKey(locale)) {
+        synchronized (sLangUserHistoryDictCache) {
+            if (sLangUserHistoryDictCache.containsKey(locale)) {
                 final SoftReference<UserHistoryPredictionDictionary> ref =
-                        sLangDictCache.get(locale);
+                        sLangUserHistoryDictCache.get(locale);
                 final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
                 if (dict != null) {
                     if (DEBUG) {
-                        Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
+                        Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
                     }
                     return dict;
                 }
             }
             final UserHistoryPredictionDictionary dict =
                     new UserHistoryPredictionDictionary(context, locale, sp);
-            sLangDictCache.put(locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+            sLangUserHistoryDictCache.put(
+                    locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+            return dict;
+        }
+    }
+
+    public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+            final Context context, final String locale, final SharedPreferences sp) {
+        synchronized (sLangPersonalizationDictCache) {
+            if (sLangPersonalizationDictCache.containsKey(locale)) {
+                final SoftReference<PersonalizationPredictionDictionary> ref =
+                        sLangPersonalizationDictCache.get(locale);
+                final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
+                if (dict != null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale);
+                    }
+                    return dict;
+                }
+            }
+            final PersonalizationPredictionDictionary dict =
+                    new PersonalizationPredictionDictionary(context, locale, sp);
+            sLangPersonalizationDictCache.put(
+                    locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
             return dict;
         }
     }