Merge "Cleanup redundant methods of KeyboardSwitcher"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
index 0aed506..d35948b 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -59,6 +59,14 @@
     private final Params mParams;
     private final KeysCache mKeysCache = new KeysCache();
 
+    public static class KeyboardSetException extends RuntimeException {
+        public final KeyboardId mKeyboardId;
+        public KeyboardSetException(Throwable cause, KeyboardId keyboardId) {
+            super(cause);
+            mKeyboardId = keyboardId;
+        }
+    }
+
     public static class KeysCache {
         private final Map<Key, Key> mMap;
 
@@ -107,11 +115,6 @@
         mParams = params;
     }
 
-    // TODO: Remove this method, use {@link #getKeyboard} directly.
-    public Keyboard getMainKeyboard() {
-        return getKeyboard(KeyboardId.ELEMENT_ALPHABET);
-    }
-
     public Keyboard getKeyboard(int baseKeyboardSetElementId) {
         final int keyboardSetElementId;
         switch (mParams.mMode) {
@@ -134,8 +137,11 @@
                     KeyboardId.ELEMENT_ALPHABET);
         }
         final KeyboardId id = getKeyboardId(keyboardSetElementId);
-        final Keyboard keyboard = getKeyboard(mContext, keyboardXmlId, id);
-        return keyboard;
+        try {
+            return getKeyboard(mContext, keyboardXmlId, id);
+        } catch (RuntimeException e) {
+            throw new KeyboardSetException(e, id);
+        }
     }
 
     private Keyboard getKeyboard(Context context, int keyboardXmlId, KeyboardId id) {
@@ -169,11 +175,10 @@
         return keyboard;
     }
 
-    // TODO: Make this method private.
     // Note: The keyboard for each locale, shift state, and mode are represented as KeyboardSet
     // element id that is a key in keyboard_set.xml.  Also that file specifies which XML layout
     // should be used for each keyboard.  The KeyboardId is an internal key for Keyboard object.
-    public KeyboardId getKeyboardId(int keyboardSetElementId) {
+    private KeyboardId getKeyboardId(int keyboardSetElementId) {
         final Params params = mParams;
         final boolean isSymbols = (keyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS
                 || keyboardSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 40bbc20..951bcdb 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -27,6 +27,8 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
+import com.android.inputmethod.keyboard.KeyboardSet.KeyboardSetException;
+import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 import com.android.inputmethod.keyboard.internal.KeyboardState;
 import com.android.inputmethod.latin.DebugSettings;
 import com.android.inputmethod.latin.InputView;
@@ -137,11 +139,9 @@
         mKeyboardSet = builder.build();
         try {
             mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
-        } catch (RuntimeException e) {
-            Log.w(TAG, "loading keyboard failed: " + mKeyboardSet.getKeyboardId(
-                    KeyboardId.ELEMENT_ALPHABET), e);
-            LatinImeLogger.logOnException(mKeyboardSet.getKeyboardId(
-                    KeyboardId.ELEMENT_ALPHABET).toString(), e);
+        } catch (KeyboardSetException e) {
+            Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
+            LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
             return;
         }
     }
@@ -253,6 +253,24 @@
         mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
     }
 
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void startDoubleTapTimer() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        if (keyboardView != null) {
+            final TimerProxy timer = keyboardView.getTimerProxy();
+            timer.startDoubleTapTimer();
+        }
+    }
+
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public boolean isInDoubleTapTimeout() {
+        final LatinKeyboardView keyboardView = getKeyboardView();
+        return (keyboardView != null)
+                ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
+    }
+
     public boolean isInMomentarySwitchState() {
         return mState.isInMomentarySwitchState();
     }
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 056aa25..5aad67d 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -29,7 +29,6 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.GestureDetector;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -64,8 +63,6 @@
         SuddenJumpingTouchEventHandler.ProcessMotionEvent {
     private static final String TAG = LatinKeyboardView.class.getSimpleName();
 
-    private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
-
     // TODO: Kill process when the usability study mode was changed.
     private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
 
@@ -111,16 +108,13 @@
     private int mOldPointerCount = 1;
     private Key mOldKey;
 
-    // To detect double tap.
-    protected GestureDetector mGestureDetector;
-
     private final KeyTimerHandler mKeyTimerHandler;
 
     private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView>
             implements TimerProxy {
         private static final int MSG_REPEAT_KEY = 1;
         private static final int MSG_LONGPRESS_KEY = 2;
-        private static final int MSG_IGNORE_DOUBLE_TAP = 3;
+        private static final int MSG_DOUBLE_TAP = 3;
         private static final int MSG_KEY_TYPED = 4;
 
         private final int mKeyRepeatInterval;
@@ -184,19 +178,20 @@
         }
 
         @Override
-        public void cancelKeyTimers() {
-            cancelKeyRepeatTimer();
-            cancelLongPressTimer();
-            removeMessages(MSG_IGNORE_DOUBLE_TAP);
-        }
-
-        public void startIgnoringDoubleTap() {
-            sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
+        public void startDoubleTapTimer() {
+            sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
                     ViewConfiguration.getDoubleTapTimeout());
         }
 
-        public boolean isIgnoringDoubleTap() {
-            return hasMessages(MSG_IGNORE_DOUBLE_TAP);
+        @Override
+        public boolean isInDoubleTapTimeout() {
+            return hasMessages(MSG_DOUBLE_TAP);
+        }
+
+        @Override
+        public void cancelKeyTimers() {
+            cancelKeyRepeatTimer();
+            cancelLongPressTimer();
         }
 
         public void cancelAllMessages() {
@@ -204,53 +199,6 @@
         }
     }
 
-    class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
-        private boolean mProcessingShiftDoubleTapEvent = false;
-
-        @Override
-        public boolean onDoubleTap(MotionEvent firstDown) {
-            final Keyboard keyboard = getKeyboard();
-            if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard.mId.isAlphabetKeyboard()) {
-                final int pointerIndex = firstDown.getActionIndex();
-                final int id = firstDown.getPointerId(pointerIndex);
-                final PointerTracker tracker = PointerTracker.getPointerTracker(
-                        id, LatinKeyboardView.this);
-                final Key key = tracker.getKeyOn((int)firstDown.getX(), (int)firstDown.getY());
-                // If the first down event is on shift key.
-                if (key != null && key.isShift()) {
-                    mProcessingShiftDoubleTapEvent = true;
-                    return true;
-                }
-            }
-            mProcessingShiftDoubleTapEvent = false;
-            return false;
-        }
-
-        @Override
-        public boolean onDoubleTapEvent(MotionEvent secondTap) {
-            if (mProcessingShiftDoubleTapEvent
-                    && secondTap.getAction() == MotionEvent.ACTION_DOWN) {
-                final MotionEvent secondDown = secondTap;
-                final int pointerIndex = secondDown.getActionIndex();
-                final int id = secondDown.getPointerId(pointerIndex);
-                final PointerTracker tracker = PointerTracker.getPointerTracker(
-                        id, LatinKeyboardView.this);
-                final Key key = tracker.getKeyOn((int)secondDown.getX(), (int)secondDown.getY());
-                // If the second down event is also on shift key.
-                if (key != null && key.isShift()) {
-                    // Detected a double tap on shift key. If we are in the ignoring double tap
-                    // mode, it means we have already turned off caps lock in
-                    // {@link KeyboardSwitcher#onReleaseShift} .
-                    onDoubleTapShiftKey(mKeyTimerHandler.isIgnoringDoubleTap());
-                    return true;
-                }
-                // Otherwise these events should not be handled as double tap.
-                mProcessingShiftDoubleTapEvent = false;
-            }
-            return mProcessingShiftDoubleTapEvent;
-        }
-    }
-
     public static class PointerTrackerParams {
         public final boolean mSlidingKeyInputEnabled;
         public final int mKeyRepeatStartTimeout;
@@ -303,11 +251,6 @@
 
         mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
 
-        final boolean ignoreMultitouch = true;
-        mGestureDetector = new GestureDetector(
-                getContext(), new DoubleTapListener(), null, ignoreMultitouch);
-        mGestureDetector.setIsLongpressEnabled(false);
-
         mHasDistinctMultitouch = context.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
 
@@ -342,11 +285,6 @@
         PointerTracker.setParameters(mPointerTrackerParams);
     }
 
-    public void startIgnoringDoubleTap() {
-        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
-            mKeyTimerHandler.startIgnoringDoubleTap();
-    }
-
     public void setKeyboardActionListener(KeyboardActionListener listener) {
         mKeyboardActionListener = listener;
         PointerTracker.setKeyboardActionListener(listener);
@@ -451,17 +389,6 @@
         return onLongPress(parentKey, tracker);
     }
 
-    private void onDoubleTapShiftKey(final boolean ignore) {
-        // When shift key is double tapped, the first tap is correctly processed as usual tap. And
-        // the second tap is treated as this double tap event, so that we need not mark tracker
-        // calling setAlreadyProcessed() nor remove the tracker from mPointerQueue.
-        if (ignore) {
-            invokeCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
-        } else {
-            invokeCodeInput(Keyboard.CODE_CAPSLOCK);
-        }
-    }
-
     // This default implementation returns a more keys panel.
     protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) {
         if (parentKey.mMoreKeys == null)
@@ -595,14 +522,6 @@
             return true;
         }
 
-        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
-        if (mMoreKeysPanel == null && mGestureDetector != null
-                && mGestureDetector.onTouchEvent(me)) {
-            PointerTracker.dismissAllKeyPreviews();
-            mKeyTimerHandler.cancelKeyTimers();
-            return true;
-        }
-
         final long eventTime = me.getEventTime();
         final int index = me.getActionIndex();
         final int id = me.getPointerId(index);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 4dac347..fc92a24 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -76,6 +76,8 @@
         public void startKeyRepeatTimer(long delay, PointerTracker tracker);
         public void startLongPressTimer(long delay, PointerTracker tracker);
         public void cancelLongPressTimer();
+        public void startDoubleTapTimer();
+        public boolean isInDoubleTapTimeout();
         public void cancelKeyTimers();
 
         public static class Adapter implements TimerProxy {
@@ -90,6 +92,10 @@
             @Override
             public void cancelLongPressTimer() {}
             @Override
+            public void startDoubleTapTimer() {}
+            @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 4af4e3c..19c644d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -51,6 +51,9 @@
          * Request to call back {@link KeyboardState#onUpdateShiftState(boolean)}.
          */
         public void requestUpdatingShiftState();
+
+        public void startDoubleTapTimer();
+        public boolean isInDoubleTapTimeout();
     }
 
     private final SwitchActions mSwitchActions;
@@ -75,6 +78,10 @@
     private boolean mPrevMainKeyboardWasShiftLocked;
     private boolean mPrevSymbolsKeyboardWasShifted;
 
+    // For handling double tap.
+    private boolean mIsInAlphabetUnshiftedFromShifted;
+    private boolean mIsInDoubleTapShiftKey;
+
     private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
 
     static class SavedKeyboardState {
@@ -256,8 +263,7 @@
         mSwitchActions.requestUpdatingShiftState();
     }
 
-    // TODO: Make this method private
-    public void setSymbolsKeyboard() {
+    private void setSymbolsKeyboard() {
         if (DEBUG_ACTION) {
             Log.d(TAG, "setSymbolsKeyboard");
         }
@@ -348,22 +354,35 @@
 
     private void onPressShift() {
         if (mIsAlphabetMode) {
-            if (mAlphabetShiftState.isShiftLocked()) {
-                // Shift key is pressed while caps lock state, we will treat this state as shifted
-                // caps lock state and mark as if shift key pressed while normal state.
+            mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapTimeout();
+            if (!mIsInDoubleTapShiftKey) {
+                // This is first tap.
+                mSwitchActions.startDoubleTapTimer();
+            }
+            if (mIsInDoubleTapShiftKey) {
+                if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
+                    // Shift key has been double tapped while in manual shifted or automatic
+                    // shifted state.
+                    setShiftLocked(true);
+                } else {
+                    // Shift key has been double tapped while in normal state. This is the second
+                    // tap to disable shift locked state, so just ignore this.
+                }
+            } else if (mAlphabetShiftState.isShiftLocked()) {
+                // Shift key is pressed while shift locked state, we will treat this state as
+                // shift lock shifted state and mark as if shift key pressed while normal state.
                 setShifted(SHIFT_LOCK_SHIFTED);
                 mShiftKeyState.onPress();
             } else if (mAlphabetShiftState.isAutomaticShifted()) {
-                // Shift key is pressed while automatic temporary upper case, we have to move to
-                // manual temporary upper case.
+                // Shift key is pressed while automatic shifted, we have to move to manual shifted.
                 setShifted(MANUAL_SHIFT);
                 mShiftKeyState.onPress();
             } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) {
-                // In manual upper case state, we just record shift key has been pressing while
+                // In manual shifted state, we just record shift key has been pressing while
                 // shifted state.
                 mShiftKeyState.onPressOnShifted();
             } else {
-                // In base layout, chording or manual temporary upper case mode is started.
+                // In base layout, chording or manual shifted mode is started.
                 setShifted(MANUAL_SHIFT);
                 mShiftKeyState.onPress();
             }
@@ -378,33 +397,40 @@
     private void onReleaseShift(boolean withSliding) {
         if (mIsAlphabetMode) {
             final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
-            if (mShiftKeyState.isChording()) {
+            mIsInAlphabetUnshiftedFromShifted = false;
+            if (mIsInDoubleTapShiftKey) {
+                // Double tap shift key has been handled in {@link #onPressShift}, so that just
+                // ignore this release shift key here.
+                mIsInDoubleTapShiftKey = false;
+            } else if (mShiftKeyState.isChording()) {
                 if (mAlphabetShiftState.isShiftLockShifted()) {
-                    // After chording input while caps lock state.
+                    // After chording input while shift locked state.
                     setShiftLocked(true);
                 } else {
                     // After chording input while normal state.
                     setShifted(UNSHIFT);
                 }
             } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) {
-                // In caps lock state, shift has been pressed and slid out to other key.
+                // In shift locked state, shift has been pressed and slid out to other key.
                 setShiftLocked(true);
             } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted()
                     && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted())
                     && !withSliding) {
                 // Shift has been long pressed, ignore this release.
             } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) {
-                // Shift has been pressed without chording while caps lock state.
+                // Shift has been pressed without chording while shift locked state.
                 setShiftLocked(false);
             } else if (mAlphabetShiftState.isShiftedOrShiftLocked()
                     && mShiftKeyState.isPressingOnShifted() && !withSliding) {
                 // Shift has been pressed without chording while shifted state.
                 setShifted(UNSHIFT);
+                mIsInAlphabetUnshiftedFromShifted = true;
             } else if (mAlphabetShiftState.isManualShiftedFromAutomaticShifted()
                     && mShiftKeyState.isPressing() && !withSliding) {
-                // Shift has been pressed without chording while manual temporary upper case
-                // transited from automatic temporary upper case.
+                // Shift has been pressed without chording while manual shifted transited from
+                // automatic shifted
                 setShifted(UNSHIFT);
+                mIsInAlphabetUnshiftedFromShifted = true;
             }
         } else {
             // In symbol mode, switch back to the previous keyboard mode if the user chords the
@@ -455,10 +481,11 @@
         if (mIsAlphabetMode && code == Keyboard.CODE_CAPSLOCK) {
             if (mAlphabetShiftState.isShiftLocked()) {
                 setShiftLocked(false);
-                // Shift key is long pressed or double tapped while caps lock state, we will
-                // toggle back to normal state. And mark as if shift key is released.
+                // Shift key is long pressed while shift locked state, we will toggle back to normal
+                // state. And mark as if shift key is released.
                 mShiftKeyState.onRelease();
             } else {
+                // Shift key is long pressed while shift unloked state.
                 setShiftLocked(true);
             }
         }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index e2feb9e..9c37cd7 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -256,18 +256,44 @@
     }
 
     // Double tap shift key.
-    // TODO: Move double tap recognizing timer/logic into KeyboardState.
     public void testDoubleTapShift() {
         // First shift key tap.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Second shift key tap.
-        // Double tap recognized in LatinKeyboardView.KeyTimerHandler.
-        secondTapShiftKey(ALPHABET_SHIFT_LOCKED);
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
 
         // First shift key tap.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
         // Second shift key tap.
-        // Second tap is ignored in LatinKeyboardView.KeyTimerHandler.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Press/release shift key, enter alphabet manual shifted.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
+
+        // Set auto caps mode on.
+        setAutoCapsMode(AUTO_CAPS);
+        // Load keyboard, should be in automatic shifted.
+        loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
+
+        // First shift key tap.
+        pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
+        // Second shift key tap.
+        secondPressAndReleaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED, ALPHABET_UNSHIFTED);
     }
 
     // Update shift state.
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
index 4055ef7..76b8436 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
@@ -64,11 +64,16 @@
         assertLayout(afterRotate, mSwitcher.getLayoutId());
     }
 
-    public void pressKey(int code, int afterPress) {
+    private void pressKeyWithoutTimerExpire(int code, int afterPress) {
         mSwitcher.onPressKey(code);
         assertLayout(afterPress, mSwitcher.getLayoutId());
     }
 
+    public void pressKey(int code, int afterPress) {
+        mSwitcher.expireDoubleTapTimeout();
+        pressKeyWithoutTimerExpire(code, afterPress);
+    }
+
     public void releaseKey(int code, int afterRelease) {
         mSwitcher.onCodeInput(code, SINGLE);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
@@ -112,8 +117,8 @@
         assertLayout(afterLongPress, mSwitcher.getLayoutId());
     }
 
-    public void secondTapShiftKey(int afterTap) {
-        mSwitcher.onCodeInput(CODE_CAPSLOCK, SINGLE);
-        assertLayout(afterTap, mSwitcher.getLayoutId());
+    public void secondPressAndReleaseKey(int code, int afterPress, int afterRelease) {
+        pressKeyWithoutTimerExpire(code, afterPress);
+        releaseKey(code, afterRelease);
     }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index f17b452..2f39340 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.keyboard.internal;
 
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.internal.KeyboardState.SwitchActions;
 
 public class MockKeyboardSwitcher implements KeyboardState.SwitchActions {
     public interface Constants {
@@ -51,6 +50,8 @@
     // Following InputConnection's behavior. Simulating InputType.TYPE_TEXT_FLAG_CAP_WORDS.
     private boolean mAutoCapsState = true;
 
+    private boolean mIsInDoubleTapTimeout;
+
     private final KeyboardState mState = new KeyboardState(this);
 
     public int getLayoutId() {
@@ -74,6 +75,10 @@
         mAutoCapsMode = autoCaps;
     }
 
+    public void expireDoubleTapTimeout() {
+        mIsInDoubleTapTimeout = false;
+    }
+
     @Override
     public void setAlphabetKeyboard() {
         mLayout = Constants.ALPHABET_UNSHIFTED;
@@ -114,6 +119,16 @@
         mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState);
     }
 
+    @Override
+    public void startDoubleTapTimer() {
+        mIsInDoubleTapTimeout = true;
+    }
+
+    @Override
+    public boolean isInDoubleTapTimeout() {
+        return mIsInDoubleTapTimeout;
+    }
+
     public void updateShiftState() {
         mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState);
     }
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index 2e36245..0c023bd 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -21,6 +21,7 @@
 
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardSet;
 
 import java.io.File;
@@ -35,22 +36,20 @@
     public SuggestHelper(Context context, int dictionaryId, KeyboardSet keyboardSet) {
         // Use null as the locale for Suggest so as to force it to use the internal dictionary
         // (and not try to find a dictionary provider for a specified locale)
-        mSuggest = new Suggest(context, dictionaryId, null);
-        mKeyboard = keyboardSet.getMainKeyboard();
-        mKeyDetector = new KeyDetector(0);
-        init();
+        this(new Suggest(context, dictionaryId, null), keyboardSet);
     }
 
     protected SuggestHelper(final Context context, final File dictionaryPath,
             final long startOffset, final long length, final KeyboardSet keyboardSet,
             final Locale locale) {
-        mSuggest = new Suggest(context, dictionaryPath, startOffset, length, null, locale);
-        mKeyboard = keyboardSet.getMainKeyboard();
-        mKeyDetector = new KeyDetector(0);
-        init();
+        this(new Suggest(context, dictionaryPath, startOffset, length, null, locale), keyboardSet);
     }
 
-    private void init() {
+    private SuggestHelper(final Suggest suggest, final KeyboardSet keyboardSet) {
+        mSuggest = suggest;
+        mKeyboard = keyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
+        mKeyDetector = new KeyDetector(0);
+
         setCorrectionMode(Suggest.CORRECTION_FULL);
         mKeyDetector.setKeyboard(mKeyboard, 0, 0);
         mKeyDetector.setProximityCorrectionEnabled(true);