Fix double tap shift key enable/disable shift locked mode

Bug: 5942452
Change-Id: I2c7b1605bceac2b2f929cd4d97c417ef15c6f754
This commit is contained in:
Tadashi G. Takaoka 2012-01-31 15:54:48 +09:00
parent 30964843db
commit 0ed2d3a449
7 changed files with 136 additions and 119 deletions

View file

@ -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();
}

View file

@ -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<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 @@ 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);

View file

@ -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() {}
}
}

View file

@ -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);
}
}

View file

@ -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.

View file

@ -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);
}
}

View file

@ -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);
}