From 0ed2d3a4491cb0f6142975a15b653be6079b6a4e Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 31 Jan 2012 15:54:48 +0900 Subject: [PATCH] Fix double tap shift key enable/disable shift locked mode Bug: 5942452 Change-Id: I2c7b1605bceac2b2f929cd4d97c417ef15c6f754 --- .../keyboard/KeyboardSwitcher.java | 19 ++++ .../keyboard/LatinKeyboardView.java | 105 ++---------------- .../inputmethod/keyboard/PointerTracker.java | 6 + .../keyboard/internal/KeyboardState.java | 61 +++++++--- .../KeyboardStateSingleTouchTests.java | 34 +++++- .../internal/KeyboardStateTestsBase.java | 13 ++- .../internal/MockKeyboardSwitcher.java | 17 ++- 7 files changed, 136 insertions(+), 119 deletions(-) diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 17ad45df0..d5ea76a2f 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -27,6 +27,7 @@ import android.view.View; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; +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; @@ -270,6 +271,24 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions, 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 056aa255d..5aad67d49 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.os.Message; 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke private int mOldPointerCount = 1; private Key mOldKey; - // To detect double tap. - protected GestureDetector mGestureDetector; - private final KeyTimerHandler mKeyTimerHandler; private static class KeyTimerHandler extends StaticInnerHandlerWrapper 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } @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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } } - 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke 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 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke 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 4dac3474d..fc92a24a6 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -76,6 +76,8 @@ public class PointerTracker { 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 @@ public class PointerTracker { @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 4af4e3cdf..19c644d1f 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 @@ public class KeyboardState { * 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 @@ public class KeyboardState { 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 @@ public class KeyboardState { mSwitchActions.requestUpdatingShiftState(); } - // TODO: Make this method private - public void setSymbolsKeyboard() { + private void setSymbolsKeyboard() { if (DEBUG_ACTION) { Log.d(TAG, "setSymbolsKeyboard"); } @@ -348,22 +354,35 @@ public class KeyboardState { 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 @@ public class KeyboardState { 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 @@ public class KeyboardState { 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 e2feb9e84..9c37cd785 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 @@ public class KeyboardStateSingleTouchTests extends KeyboardStateTestsBase { } // 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 4055ef727..76b84364c 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 @@ public class KeyboardStateTestsBase extends AndroidTestCase 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 @@ public class KeyboardStateTestsBase extends AndroidTestCase 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 f17b45235..2f39340c9 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 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { // 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 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { mAutoCapsMode = autoCaps; } + public void expireDoubleTapTimeout() { + mIsInDoubleTapTimeout = false; + } + @Override public void setAlphabetKeyboard() { mLayout = Constants.ALPHABET_UNSHIFTED; @@ -114,6 +119,16 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState); } + @Override + public void startDoubleTapTimer() { + mIsInDoubleTapTimeout = true; + } + + @Override + public boolean isInDoubleTapTimeout() { + return mIsInDoubleTapTimeout; + } + public void updateShiftState() { mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState); }