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
diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
index a7b695e..0db204e 100644
--- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
@@ -82,10 +82,6 @@
         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 @@
         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 @@
     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 @@
 
     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 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 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 @@
         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;
                 }
-                break;
-            case SYMBOLS_MODE_STATE_SYMBOL:
-                if (key == LatinIME.KEYCODE_ENTER || key == LatinIME.KEYCODE_SPACE) return true;
-                break;
+            } 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 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() {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index ffca22e..b1689f8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -342,6 +342,7 @@
     @Override
     public void onCreate() {
         LatinImeLogger.init(this);
+        KeyboardSwitcher.init(this);
         super.onCreate();
         //setStatusIcon(R.drawable.ime_qwerty);
         mResources = getResources();
@@ -349,7 +350,7 @@
         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 @@
                 // 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 @@
         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 void onCancel() {
         // User released a finger outside any key
+        mKeyboardSwitcher.onCancelInput();
     }
 
     private void handleBackspace() {
@@ -2283,15 +2284,18 @@
     }
 
     public void onPress(int primaryCode) {
-        vibrate();
-        playKeyClick(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 @@
                 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 @@
         mOptionsDialog.show();
     }
 
-    private void changeKeyboardMode() {
+    public void changeKeyboardMode() {
         mKeyboardSwitcher.toggleSymbols();
         if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
             mKeyboardSwitcher.setShiftLocked(mCapsLock);
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
index b1ef085..008d372 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
@@ -343,7 +343,7 @@
             cancelPopupPreview();
             cancelDismissPreview();
         }
-    };
+    }
 
     static class PointerQueue {
         private LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
@@ -391,6 +391,14 @@
         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 void onCancel() {
+                mKeyboardActionListener.onCancel();
                 dismissPopupKeyboard();
             }
 
@@ -1294,15 +1303,29 @@
         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 @@
         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 @@
                 Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
                         + " (old " + oldPointerCount + ")");
             }
-            mOldPointerCount = pointerCount;
             return true;
         }
 
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java
index 90218e4..b23b4d7 100644
--- a/java/src/com/android/inputmethod/latin/PointerTracker.java
+++ b/java/src/com/android/inputmethod/latin/PointerTracker.java
@@ -51,6 +51,7 @@
     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 @@
 
     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 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 @@
         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 @@
             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 @@
         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 @@
                 mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
                 mIsRepeatableKey = true;
             }
-            mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
+            startLongPressTimer(keyIndex);
         }
         showKeyPreviewAndUpdateKey(keyIndex);
     }
@@ -295,42 +309,70 @@
             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 @@
             x = mKeyState.getKeyX();
             y = mKeyState.getKeyY();
         }
-        showKeyPreviewAndUpdateKey(NOT_A_KEY);
         if (!mIsRepeatableKey) {
             detectAndSendKey(keyIndex, x, y, eventTime);
         }
@@ -353,6 +394,7 @@
         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 @@
         }
     }
 
+    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 @@
             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