Merge "Import translations."
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index bff491f..0ce98d2 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -180,32 +180,34 @@
         if (maxCodesSize <= numCodes) {
             return;
         }
-        if (primaryCode != NOT_A_CODE) {
-            final List<Integer> additionalChars =
-                    mKeyboard.getAdditionalProximityChars().get(primaryCode);
-            if (additionalChars == null || additionalChars.size() == 0) {
-                return;
-            }
-            int currentCodesSize = numCodes;
-            allCodes[numCodes++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
-            if (maxCodesSize <= numCodes) {
-                return;
-            }
-            // TODO: This is O(N^2). Assuming additionalChars.size() is up to 4 or 5.
-            for (int i = 0; i < additionalChars.size(); ++i) {
-                final int additionalChar = additionalChars.get(i);
-                boolean contains = false;
-                for (int j = 0; j < currentCodesSize; ++j) {
-                    if (additionalChar == allCodes[j]) {
-                        contains = true;
-                        break;
-                    }
+
+        final int code = (primaryCode == NOT_A_CODE) ? allCodes[0] : primaryCode;
+        if (code == NOT_A_CODE) {
+            return;
+        }
+        final List<Integer> additionalChars = mKeyboard.getAdditionalProximityChars().get(code);
+        if (additionalChars == null || additionalChars.size() == 0) {
+            return;
+        }
+        int currentCodesSize = numCodes;
+        allCodes[numCodes++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+        if (maxCodesSize <= numCodes) {
+            return;
+        }
+        // TODO: This is O(N^2). Assuming additionalChars.size() is up to 4 or 5.
+        for (int i = 0; i < additionalChars.size(); ++i) {
+            final int additionalChar = additionalChars.get(i);
+            boolean contains = false;
+            for (int j = 0; j < currentCodesSize; ++j) {
+                if (additionalChar == allCodes[j]) {
+                    contains = true;
+                    break;
                 }
-                if (!contains) {
-                    allCodes[numCodes++] = additionalChar;
-                    if (maxCodesSize <= numCodes) {
-                        return;
-                    }
+            }
+            if (!contains) {
+                allCodes[numCodes++] = additionalChar;
+                if (maxCodesSize <= numCodes) {
+                    return;
                 }
             }
         }
@@ -257,10 +259,17 @@
 
     public static String printableCodes(int[] codes) {
         final StringBuilder sb = new StringBuilder();
+        boolean addDelimiter = false;
         for (final int code : codes) {
             if (code == NOT_A_CODE) break;
-            if (sb.length() > 0) sb.append(", ");
-            sb.append(code);
+            if (code == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+                sb.append(" | ");
+                addDelimiter = false;
+            } else {
+                if (addDelimiter) sb.append(", ");
+                sb.append(code);
+                addDelimiter = true;
+            }
         }
         return "[" + sb + "]";
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 5ba560d..d5e5a4e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -268,6 +268,16 @@
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
+    public void cancelDoubleTapTimer() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        if (keyboardView != null) {
+            final TimerProxy timer = keyboardView.getTimerProxy();
+            timer.cancelDoubleTapTimer();
+        }
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
     public boolean isInDoubleTapTimeout() {
         final LatinKeyboardView keyboardView = getKeyboardView();
         return (keyboardView != null)
@@ -286,6 +296,16 @@
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
+    public void cancelLongPressTimer() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        if (keyboardView != null) {
+            final TimerProxy timer = keyboardView.getTimerProxy();
+            timer.cancelLongPressTimer();
+        }
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
     public void hapticAndAudioFeedback(int code) {
         mInputMethodService.hapticAndAudioFeedback(code);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 88a4157..9a0fe1e 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -233,6 +233,11 @@
         }
 
         @Override
+        public void cancelDoubleTapTimer() {
+            removeMessages(MSG_DOUBLE_TAP);
+        }
+
+        @Override
         public boolean isInDoubleTapTimeout() {
             return hasMessages(MSG_DOUBLE_TAP);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 110f7f6..c453084 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -77,6 +77,7 @@
         public void startLongPressTimer(int code);
         public void cancelLongPressTimer();
         public void startDoubleTapTimer();
+        public void cancelDoubleTapTimer();
         public boolean isInDoubleTapTimeout();
         public void cancelKeyTimers();
 
@@ -96,6 +97,8 @@
             @Override
             public void startDoubleTapTimer() {}
             @Override
+            public void cancelDoubleTapTimer() {}
+            @Override
             public boolean isInDoubleTapTimeout() { return false; }
             @Override
             public void cancelKeyTimers() {}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index cb8b4f0..6a8a036 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -54,7 +54,9 @@
 
         public void startDoubleTapTimer();
         public boolean isInDoubleTapTimeout();
+        public void cancelDoubleTapTimer();
         public void startLongPressTimer(int code);
+        public void cancelLongPressTimer();
         public void hapticAndAudioFeedback(int code);
     }
 
@@ -300,6 +302,8 @@
         } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
             onPressSymbol();
         } else {
+            mSwitchActions.cancelDoubleTapTimer();
+            mSwitchActions.cancelLongPressTimer();
             mShiftKeyState.onOtherKeyPressed();
             mSymbolKeyState.onOtherKeyPressed();
         }
@@ -348,7 +352,7 @@
                 // state. And mark as if shift key is released.
                 mShiftKeyState.onRelease();
             } else {
-                // Shift key is long pressed while shift unloked state.
+                // Shift key is long pressed while shift unlocked state.
                 setShiftLocked(true);
             }
             mSwitchActions.hapticAndAudioFeedback(code);
@@ -364,6 +368,11 @@
 
     private void updateAlphabetShiftState(boolean autoCaps) {
         if (!mIsAlphabetMode) return;
+        if (!mShiftKeyState.isReleasing()) {
+            // Ignore update shift state event while the shift key is being pressed (including
+            // chording).
+            return;
+        }
         if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) {
             if (mShiftKeyState.isReleasing() && autoCaps) {
                 // Only when shift key is releasing, automatic temporary upper case will be set.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index d9181f7..5db65c6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -16,12 +16,18 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.util.Log;
+
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.PointerTracker;
 
 import java.util.Iterator;
 import java.util.LinkedList;
 
 public class PointerTrackerQueue {
+    private static final String TAG = PointerTrackerQueue.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
     private final LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
 
     public synchronized void add(PointerTracker tracker) {
@@ -32,7 +38,11 @@
         mQueue.remove(tracker);
     }
 
-    public synchronized void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) {
+    public synchronized void releaseAllPointersOlderThan(PointerTracker tracker,
+            long eventTime) {
+        if (DEBUG) {
+            Log.d(TAG, "releaseAllPoniterOlderThan: [" + tracker.mPointerId + "] " + this);
+        }
         if (!mQueue.contains(tracker)) {
             return;
         }
@@ -54,6 +64,13 @@
     }
 
     public synchronized void releaseAllPointersExcept(PointerTracker tracker, long eventTime) {
+        if (DEBUG) {
+            if (tracker == null) {
+                Log.d(TAG, "releaseAllPoniters: " + this);
+            } else {
+                Log.d(TAG, "releaseAllPoniterExcept: [" + tracker.mPointerId + "] " + this);
+            }
+        }
         final Iterator<PointerTracker> it = mQueue.iterator();
         while (it.hasNext()) {
             final PointerTracker t = it.next();
@@ -79,8 +96,9 @@
         for (final PointerTracker tracker : mQueue) {
             if (sb.length() > 0)
                 sb.append(" ");
-            sb.append(tracker.mPointerId);
+            sb.append("[" + tracker.mPointerId + " "
+                + Keyboard.printableCode(tracker.getKey().mCode) + "]");
         }
-        return "[" + sb + "]";
+        return sb.toString();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 1cb79e7..730992b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -325,7 +325,7 @@
             return hasMessages(MSG_UPDATE_SUGGESTIONS);
         }
 
-        public void postUpdateShiftKeyState() {
+        public void postUpdateShiftState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
             sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
         }
@@ -898,9 +898,10 @@
                 resetComposingState(true /* alsoResetLastComposedWord */);
                 updateSuggestions();
             }
+
+            mHandler.postUpdateShiftState();
         }
         mExpectingUpdateSelection = false;
-        mHandler.postUpdateShiftKeyState();
         // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
         // here. It would probably be too expensive to call directly here but we may want to post a
         // message to delay it. The point would be to unify behavior between backspace to the
@@ -1391,7 +1392,7 @@
         mVoiceProxy.handleBackspace();
 
         // In many cases, we may have to put the keyboard in auto-shift state again.
-        mHandler.postUpdateShiftKeyState();
+        mHandler.postUpdateShiftState();
 
         if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
             // Cancel multi-character input: remove the text we just entered.
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
index e9f37af..0e6caaf 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
@@ -227,4 +227,17 @@
         // Release "123?" key, switch back to alphabet.
         releaseKey(CODE_SYMBOL, ALPHABET_UNSHIFTED);
     }
+
+    // Chording letter key with shift key.
+    public void testChordingLetterAndShiftKey() {
+        // Press letter key and hold.
+        pressKey('z', ALPHABET_UNSHIFTED);
+        // Press shift key, {@link PointerTracker} will fire a phantom release letter key.
+        chordingReleaseKey('z', ALPHABET_UNSHIFTED);
+        chordingPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
+        // Press another letter key and hold.
+        chordingPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Release shift key
+        releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index 8c53fb5..ef0facf 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -687,4 +687,67 @@
         // Press/release "?123" key, enter symbols (not symbols shifted).
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
     }
+
+    // Rapidly type shift key.
+    public void testRapidShiftTyping() {
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key to enter alphabet manual shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('j', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Long press shift key to enter alphabet shift locked.
+        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_SHIFT_LOCKED);
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('j', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+        // Press/release auto caps trigger letter to enter alphabet automatic shifted.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+        // Press/release shift key
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('j', ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Rapidly press/release shift key.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        // Rapidly press/release letter key.
+        secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index a01b69c..999e08a 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -125,6 +125,11 @@
     }
 
     @Override
+    public void cancelDoubleTapTimer() {
+        mIsInDoubleTapTimeout = false;
+    }
+
+    @Override
     public boolean isInDoubleTapTimeout() {
         return mIsInDoubleTapTimeout;
     }
@@ -135,6 +140,11 @@
     }
 
     @Override
+    public void cancelLongPressTimer() {
+        mLongPressTimeoutCode = 0;
+    }
+
+    @Override
     public void hapticAndAudioFeedback(int code) {
         // Nothing to do.
     }