Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0ed2d3a4 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Fix double tap shift key enable/disable shift locked mode

Bug: 5942452
Change-Id: I2c7b1605bceac2b2f929cd4d97c417ef15c6f754
parent 30964843
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -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();
    }
+11 −92
Original line number Diff line number Diff line
@@ -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,70 +178,24 @@ 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);
        }

        public void cancelAllMessages() {
            cancelKeyTimers();
        }
    }

    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;
        public boolean isInDoubleTapTimeout() {
            return hasMessages(MSG_DOUBLE_TAP);
        }

        @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;
        public void cancelKeyTimers() {
            cancelKeyRepeatTimer();
            cancelLongPressTimer();
        }
            return mProcessingShiftDoubleTapEvent;

        public void cancelAllMessages() {
            cancelKeyTimers();
        }
    }

@@ -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);
+6 −0
Original line number Diff line number Diff line
@@ -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() {}
        }
    }
+44 −17
Original line number Diff line number Diff line
@@ -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);
            }
        }
+30 −4
Original line number Diff line number Diff line
@@ -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.
Loading