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

Commit 7aedc8a0 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Automaticaly snap back to the previous mode from sliding input (DO NOT MERGE)

This change is back porting of the following changes from Honeycomb.
- I48ea1346: Automaticaly snap back to the previous mode from sliding symbol input
- I9507a98c: Suppress haptic feedback while sliding key input
- Ia06e1abc: Cancel long press timer when sliding key input is not allowed
- I15127929: Fix checking of sliding off from key
- I2518dd1d: Fix potential keyboard layout change bug
- Iffaad1eb: Snap back to the previous keyboard when sliding input is canceled
- Id74bddef: Longer long-press timeout in sliding input

Bug: 3280151
Change-Id: If20b34e8773ebf081c2274d136be4f8ad07ca4fa
parent 82496fa1
Loading
Loading
Loading
Loading
+98 −30
Original line number Diff line number Diff line
@@ -82,10 +82,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
        R.xml.kbd_symbols_shift, R.xml.kbd_symbols_shift_black};
    private static final int[] KBD_QWERTY = new int[] {R.xml.kbd_qwerty, R.xml.kbd_qwerty_black};

    private static final int SYMBOLS_MODE_STATE_NONE = 0;
    private static final int SYMBOLS_MODE_STATE_BEGIN = 1;
    private static final int SYMBOLS_MODE_STATE_SYMBOL = 2;

    private LatinKeyboardView mInputView;
    private static final int[] ALPHABET_MODES = {
        KEYBOARDMODE_NORMAL,
@@ -99,13 +95,14 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
        KEYBOARDMODE_IM_WITH_SETTINGS_KEY,
        KEYBOARDMODE_WEB_WITH_SETTINGS_KEY };

    private final LatinIME mInputMethodService;
    private LatinIME mInputMethodService;

    private KeyboardId mSymbolsId;
    private KeyboardId mSymbolsShiftedId;

    private KeyboardId mCurrentId;
    private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboards;
    private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboards =
            new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();

    private int mMode = MODE_NONE; /** One of the MODE_XXX values */
    private int mImeOptions;
@@ -116,7 +113,14 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
    private boolean mHasVoice;
    private boolean mVoiceOnPrimary;
    private boolean mPreferSymbols;
    private int mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;

    private static final int AUTO_MODE_SWITCH_STATE_ALPHA = 0;
    private static final int AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN = 1;
    private static final int AUTO_MODE_SWITCH_STATE_SYMBOL = 2;
    // The following states are used only on the distinct multi-touch panel devices.
    private static final int AUTO_MODE_SWITCH_STATE_MOMENTARY = 3;
    private static final int AUTO_MODE_SWITCH_STATE_CHORDING = 4;
    private int mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;

    // Indicates whether or not we have the settings key
    private boolean mHasSettingsKey;
@@ -133,17 +137,27 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha

    private int mLayoutId;

    public KeyboardSwitcher(LatinIME ims) {
        mInputMethodService = ims;
    private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();

    public static KeyboardSwitcher getInstance() {
        return sInstance;
    }

    private KeyboardSwitcher() {
        // Intentional empty constructor for singleton.
    }

    public static void init(LatinIME ims) {
        sInstance.mInputMethodService = ims;

        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ims);
        mLayoutId = Integer.valueOf(prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID));
        updateSettingsKeyState(prefs);
        prefs.registerOnSharedPreferenceChangeListener(this);
        sInstance.mLayoutId = Integer.valueOf(
                prefs.getString(PREF_KEYBOARD_LAYOUT, DEFAULT_LAYOUT_ID));
        sInstance.updateSettingsKeyState(prefs);
        prefs.registerOnSharedPreferenceChangeListener(sInstance);

        mKeyboards = new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
        mSymbolsId = makeSymbolsId(false);
        mSymbolsShiftedId = makeSymbolsShiftedId(false);
        sInstance.mSymbolsId = sInstance.makeSymbolsId(false);
        sInstance.mSymbolsShiftedId = sInstance.makeSymbolsShiftedId(false);
    }

    /**
@@ -243,7 +257,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
    }

    public void setKeyboardMode(int mode, int imeOptions, boolean enableVoice) {
        mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
        mPreferSymbols = mode == MODE_SYMBOLS;
        if (mode == MODE_SYMBOLS) {
            mode = MODE_TEXT;
@@ -410,12 +424,18 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
        }
    }

    public void onCancelInput() {
        // Snap back to the previous keyboard mode if the user cancels sliding input.
        if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY && getPointerCount() == 1)
            mInputMethodService.changeKeyboardMode();
    }

    public void toggleSymbols() {
        setKeyboardMode(mMode, mImeOptions, mHasVoice, !mIsSymbols);
        if (mIsSymbols && !mPreferSymbols) {
            mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN;
            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
        } else {
            mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
        }
    }

@@ -423,24 +443,72 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
        return mInputView != null && mInputView.hasDistinctMultitouch();
    }

    public void setAutoModeSwitchStateMomentary() {
        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_MOMENTARY;
    }

    public boolean isInMomentaryAutoModeSwitchState() {
        return mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY;
    }

    public boolean isInChordingAutoModeSwitchState() {
        return mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_CHORDING;
    }

    public boolean isVibrateAndSoundFeedbackRequired() {
        return mInputView != null && !mInputView.isInSlidingKeyInput();
    }

    private int getPointerCount() {
        return mInputView == null ? 0 : mInputView.getPointerCount();
    }

    /**
     * Updates state machine to figure out when to automatically switch back to alpha mode.
     * Returns true if the keyboard needs to switch back 
     * Updates state machine to figure out when to automatically snap back to the previous mode.
     */
    public boolean onKey(int key) {
    public void onKey(int key) {
        // Switch back to alpha mode if user types one or more non-space/enter characters
        // followed by a space/enter
        switch (mSymbolsModeState) {
            case SYMBOLS_MODE_STATE_BEGIN:
                if (key != LatinIME.KEYCODE_SPACE && key != LatinIME.KEYCODE_ENTER && key > 0) {
                    mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL;
        switch (mAutoModeSwitchState) {
        case AUTO_MODE_SWITCH_STATE_MOMENTARY:
            // Only distinct multi touch devices can be in this state.
            // On non-distinct multi touch devices, mode change key is handled by {@link onKey},
            // not by {@link onPress} and {@link onRelease}. So, on such devices,
            // {@link mAutoModeSwitchState} starts from {@link AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN},
            // or {@link AUTO_MODE_SWITCH_STATE_ALPHA}, not from
            // {@link AUTO_MODE_SWITCH_STATE_MOMENTARY}.
            if (key == LatinKeyboard.KEYCODE_MODE_CHANGE) {
                // Detected only the mode change key has been pressed, and then released.
                if (mIsSymbols) {
                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
                } else {
                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
                }
            } else if (getPointerCount() == 1) {
                // Snap back to the previous keyboard mode if the user pressed the mode change key
                // and slid to other key, then released the finger.
                // If the user cancels the sliding input, snapping back to the previous keyboard
                // mode is handled by {@link #onCancelInput}.
                mInputMethodService.changeKeyboardMode();
            } else {
                // Chording input is being started. The keyboard mode will be snapped back to the
                // previous mode in {@link onReleaseSymbol} when the mode change key is released.
                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_CHORDING;
            }
            break;
        case AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN:
            if (key != LatinIME.KEYCODE_SPACE && key != LatinIME.KEYCODE_ENTER && key >= 0) {
                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL;
            }
            break;
            case SYMBOLS_MODE_STATE_SYMBOL:
                if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) return true;
        case AUTO_MODE_SWITCH_STATE_SYMBOL:
            // Snap back to alpha keyboard mode if user types one or more non-space/enter
            // characters followed by a space/enter.
            if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) {
                mInputMethodService.changeKeyboardMode();
            }
            break;
        }
        return false;
    }

    public LatinKeyboardView getInputView() {
+15 −9
Original line number Diff line number Diff line
@@ -342,6 +342,7 @@ public class LatinIME extends InputMethodService
    @Override
    public void onCreate() {
        LatinImeLogger.init(this);
        KeyboardSwitcher.init(this);
        super.onCreate();
        //setStatusIcon(R.drawable.ime_qwerty);
        mResources = getResources();
@@ -349,7 +350,7 @@ public class LatinIME extends InputMethodService
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        mLanguageSwitcher = new LanguageSwitcher(this);
        mLanguageSwitcher.loadLocales(prefs);
        mKeyboardSwitcher = new KeyboardSwitcher(this);
        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
        mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
        mSystemLocale = conf.locale.toString();
        mLanguageSwitcher.setSystemLocale(conf.locale);
@@ -1247,9 +1248,7 @@ public class LatinIME extends InputMethodService
                // Cancel the just reverted state
                mJustRevertedSeparator = null;
        }
        if (mKeyboardSwitcher.onKey(primaryCode)) {
            changeKeyboardMode();
        }
        mKeyboardSwitcher.onKey(primaryCode);
        // Reset after any single keystroke
        mEnteredText = null;
    }
@@ -1269,6 +1268,7 @@ public class LatinIME extends InputMethodService
        ic.commitText(text, 1);
        ic.endBatchEdit();
        updateShiftKeyState(getCurrentInputEditorInfo());
        mKeyboardSwitcher.onKey(0); // dummy key code.
        mJustRevertedSeparator = null;
        mJustAddedAutoSpace = false;
        mEnteredText = text;
@@ -1276,6 +1276,7 @@ public class LatinIME extends InputMethodService

    public void onCancel() {
        // User released a finger outside any key
        mKeyboardSwitcher.onCancelInput();
    }

    private void handleBackspace() {
@@ -2283,15 +2284,18 @@ public class LatinIME extends InputMethodService
    }

    public void onPress(int primaryCode) {
        if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) {
            vibrate();
            playKeyClick(primaryCode);
        }
        final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
        if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_SHIFT) {
            mShiftKeyState.onPress();
            handleShift();
        } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
            mSymbolKeyState.onPress();
            changeKeyboardMode();
            mSymbolKeyState.onPress();
            mKeyboardSwitcher.setAutoModeSwitchStateMomentary();
        } else {
            mShiftKeyState.onOtherKeyPressed();
            mSymbolKeyState.onOtherKeyPressed();
@@ -2308,7 +2312,9 @@ public class LatinIME extends InputMethodService
                resetShift();
            mShiftKeyState.onRelease();
        } else if (distinctMultiTouch && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
            if (mSymbolKeyState.isMomentary())
            // Snap back to the previous keyboard mode if the user chords the mode change key and
            // other key, then released the mode change key.
            if (mKeyboardSwitcher.isInChordingAutoModeSwitchState())
                changeKeyboardMode();
            mSymbolKeyState.onRelease();
        }
@@ -2562,7 +2568,7 @@ public class LatinIME extends InputMethodService
        mOptionsDialog.show();
    }

    private void changeKeyboardMode() {
    public void changeKeyboardMode() {
        mKeyboardSwitcher.toggleSymbols();
        if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
            mKeyboardSwitcher.setShiftLocked(mCapsLock);
+26 −5
Original line number Diff line number Diff line
@@ -343,7 +343,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
            cancelPopupPreview();
            cancelDismissPreview();
        }
    };
    }

    static class PointerQueue {
        private LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
@@ -391,6 +391,14 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
        public void remove(PointerTracker tracker) {
            mQueue.remove(tracker);
        }

        public boolean isInSlidingKeyInput() {
            for (final PointerTracker tracker : mQueue) {
                if (tracker.isInSlidingKeyInput())
                    return true;
            }
            return false;
        }
    }

    public LatinKeyboardBaseView(Context context, AttributeSet attrs) {
@@ -1086,6 +1094,7 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
            }

            public void onCancel() {
                mKeyboardActionListener.onCancel();
                dismissPopupKeyboard();
            }

@@ -1294,15 +1303,29 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
        return pointers.get(id);
    }

    public boolean isInSlidingKeyInput() {
        if (mMiniKeyboard != null) {
            return mMiniKeyboard.isInSlidingKeyInput();
        } else {
            return mPointerQueue.isInSlidingKeyInput();
        }
    }

    public int getPointerCount() {
        return mOldPointerCount;
    }

    @Override
    public boolean onTouchEvent(MotionEvent me) {
        final int pointerCount = me.getPointerCount();
        final int action = me.getActionMasked();
        final int pointerCount = me.getPointerCount();
        final int oldPointerCount = mOldPointerCount;
        mOldPointerCount = pointerCount;

        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
        // If the device does not have distinct multi-touch support panel, ignore all multi-touch
        // events except a transition from/to single-touch.
        if (!mHasDistinctMultitouch && pointerCount > 1 && mOldPointerCount > 1) {
        if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
            return true;
        }

@@ -1358,7 +1381,6 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
        if (!mHasDistinctMultitouch) {
            // Use only main (id=0) pointer tracker.
            PointerTracker tracker = getPointerTracker(0);
            int oldPointerCount = mOldPointerCount;
            if (pointerCount == 1 && oldPointerCount == 2) {
                // Multi-touch to single touch transition.
                // Send a down event for the latest pointer.
@@ -1373,7 +1395,6 @@ public class LatinKeyboardBaseView extends View implements PointerTracker.UIProx
                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
                        + " (old " + oldPointerCount + ")");
            }
            mOldPointerCount = pointerCount;
            return true;
        }

+74 −24
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ public class PointerTracker {
    private final UIHandler mHandler;
    private final KeyDetector mKeyDetector;
    private OnKeyboardActionListener mListener;
    private final KeyboardSwitcher mKeyboardSwitcher;
    private final boolean mHasDistinctMultitouch;

    private Key[] mKeys;
@@ -58,12 +59,18 @@ public class PointerTracker {

    private final KeyState mKeyState;

    // true if keyboard layout has been changed.
    private boolean mKeyboardLayoutHasBeenChanged;

    // true if event is already translated to a key action (long press or mini-keyboard)
    private boolean mKeyAlreadyProcessed;

    // true if this pointer is repeatable key
    private boolean mIsRepeatableKey;

    // true if this pointer is in sliding key input
    private boolean mIsInSlidingKeyInput;

    // For multi-tap
    private int mLastSentIndex;
    private int mTapCount;
@@ -157,10 +164,6 @@ public class PointerTracker {
        public int onUpKey(int x, int y) {
            return onMoveKeyInternal(x, y);
        }

        public void onSetKeyboard() {
            mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null);
        }
    }

    public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
@@ -171,6 +174,7 @@ public class PointerTracker {
        mProxy = proxy;
        mHandler = handler;
        mKeyDetector = keyDetector;
        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
        mKeyState = new KeyState(keyDetector);
        mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
        mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
@@ -188,8 +192,12 @@ public class PointerTracker {
            throw new IllegalArgumentException();
        mKeys = keys;
        mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
        // Update current key index because keyboard layout has been changed.
        mKeyState.onSetKeyboard();
        // Mark that keyboard layout has been changed.
        mKeyboardLayoutHasBeenChanged = true;
    }

    public boolean isInSlidingKeyInput() {
        return mIsInSlidingKeyInput;
    }

    private boolean isValidKeyIndex(int keyIndex) {
@@ -268,15 +276,21 @@ public class PointerTracker {
        if (DEBUG)
            debugLog("onDownEvent:", x, y);
        int keyIndex = mKeyState.onDownKey(x, y, eventTime);
        mKeyboardLayoutHasBeenChanged = false;
        mKeyAlreadyProcessed = false;
        mIsRepeatableKey = false;
        mIsInSlidingKeyInput = false;
        checkMultiTap(eventTime, keyIndex);
        if (mListener != null) {
            if (isValidKeyIndex(keyIndex)) {
                mListener.onPress(mKeys[keyIndex].codes[0]);
                // This onPress call may have changed keyboard layout and have updated mKeyIndex.
                // If that's the case, mKeyIndex has been updated in setKeyboard().
                keyIndex = mKeyState.getKeyIndex();
                // This onPress call may have changed keyboard layout. Those cases are detected at
                // {@link #setKeyboard}. In those cases, we should update keyIndex according to the
                // new keyboard layout.
                if (mKeyboardLayoutHasBeenChanged) {
                    mKeyboardLayoutHasBeenChanged = false;
                    keyIndex = mKeyState.onDownKey(x, y, eventTime);
                }
            }
        }
        if (isValidKeyIndex(keyIndex)) {
@@ -285,7 +299,7 @@ public class PointerTracker {
                mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
                mIsRepeatableKey = true;
            }
            mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
            startLongPressTimer(keyIndex);
        }
        showKeyPreviewAndUpdateKey(keyIndex);
    }
@@ -295,42 +309,70 @@ public class PointerTracker {
            debugLog("onMoveEvent:", x, y);
        if (mKeyAlreadyProcessed)
            return;
        KeyState keyState = mKeyState;
        final int keyIndex = keyState.onMoveKey(x, y);
        final KeyState keyState = mKeyState;
        int keyIndex = keyState.onMoveKey(x, y);
        final Key oldKey = getKey(keyState.getKeyIndex());
        if (isValidKeyIndex(keyIndex)) {
            if (oldKey == null) {
                // The pointer has been slid in to the new key, but the finger was not on any keys.
                // In this case, we must call onPress() to notify that the new key is being pressed.
                if (mListener != null) {
                    mListener.onPress(getKey(keyIndex).codes[0]);
                    // This onPress call may have changed keyboard layout. Those cases are detected
                    // at {@link #setKeyboard}. In those cases, we should update keyIndex according
                    // to the new keyboard layout.
                    if (mKeyboardLayoutHasBeenChanged) {
                        mKeyboardLayoutHasBeenChanged = false;
                        keyIndex = keyState.onMoveKey(x, y);
                    }
                }
                keyState.onMoveToNewKey(keyIndex, x, y);
                mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
                startLongPressTimer(keyIndex);
            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
                // The pointer has been slid in to the new key from the previous key, we must call
                // onRelease() first to notify that the previous key has been released, then call
                // onPress() to notify that the new key is being pressed.
                mIsInSlidingKeyInput = true;
                if (mListener != null)
                    mListener.onRelease(oldKey.codes[0]);
                resetMultiTap();
                if (mListener != null) {
                    mListener.onPress(getKey(keyIndex).codes[0]);
                    // This onPress call may have changed keyboard layout. Those cases are detected
                    // at {@link #setKeyboard}. In those cases, we should update keyIndex according
                    // to the new keyboard layout.
                    if (mKeyboardLayoutHasBeenChanged) {
                        mKeyboardLayoutHasBeenChanged = false;
                        keyIndex = keyState.onMoveKey(x, y);
                    }
                }
                keyState.onMoveToNewKey(keyIndex, x, y);
                mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
                startLongPressTimer(keyIndex);
            }
        } else {
            if (oldKey != null) {
            if (oldKey != null && !isMinorMoveBounce(x, y, keyIndex)) {
                // The pointer has been slid out from the previous key, we must call onRelease() to
                // notify that the previous key has been released.
                mIsInSlidingKeyInput = true;
                if (mListener != null)
                    mListener.onRelease(oldKey.codes[0]);
                keyState.onMoveToNewKey(keyIndex, x ,y);
                mHandler.cancelLongPressTimer();
            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
                resetMultiTap();
                keyState.onMoveToNewKey(keyIndex, x ,y);
                mHandler.cancelLongPressTimer();
            }
        }
        showKeyPreviewAndUpdateKey(mKeyState.getKeyIndex());
        showKeyPreviewAndUpdateKey(keyState.getKeyIndex());
    }

    public void onUpEvent(int x, int y, long eventTime) {
        if (DEBUG)
            debugLog("onUpEvent  :", x, y);
        if (mKeyAlreadyProcessed)
            return;
        mHandler.cancelKeyTimers();
        mHandler.cancelPopupPreview();
        showKeyPreviewAndUpdateKey(NOT_A_KEY);
        mIsInSlidingKeyInput = false;
        if (mKeyAlreadyProcessed)
            return;
        int keyIndex = mKeyState.onUpKey(x, y);
        if (isMinorMoveBounce(x, y, keyIndex)) {
            // Use previous fixed key index and coordinates.
@@ -338,7 +380,6 @@ public class PointerTracker {
            x = mKeyState.getKeyX();
            y = mKeyState.getKeyY();
        }
        showKeyPreviewAndUpdateKey(NOT_A_KEY);
        if (!mIsRepeatableKey) {
            detectAndSendKey(keyIndex, x, y, eventTime);
        }
@@ -353,6 +394,7 @@ public class PointerTracker {
        mHandler.cancelKeyTimers();
        mHandler.cancelPopupPreview();
        showKeyPreviewAndUpdateKey(NOT_A_KEY);
        mIsInSlidingKeyInput = false;
        int keyIndex = mKeyState.getKeyIndex();
        if (isValidKeyIndex(keyIndex))
           mProxy.invalidateKey(mKeys[keyIndex]);
@@ -425,6 +467,15 @@ public class PointerTracker {
        }
    }

    private void startLongPressTimer(int keyIndex) {
        if (mKeyboardSwitcher.isInMomentaryAutoModeSwitchState()) {
            // We use longer timeout for sliding finger input started from the symbols mode key.
            mHandler.startLongPressTimer(mLongPressKeyTimeout * 3, keyIndex, this);
        } else {
            mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
        }
    }

    private void detectAndSendKey(int index, int x, int y, long eventTime) {
        final OnKeyboardActionListener listener = mListener;
        final Key key = getKey(index);
@@ -436,11 +487,10 @@ public class PointerTracker {
            if (key.text != null) {
                if (listener != null) {
                    listener.onText(key.text);
                    listener.onRelease(NOT_A_KEY);
                    listener.onRelease(0); // dummy key code
                }
            } else {
                int code = key.codes[0];
                //TextEntryState.keyPressedAt(key, x, y);
                int[] codes = mKeyDetector.newCodeArray();
                mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
                // Multi-tap