Automaticaly snap back to the previous mode from sliding symbol input

Bug: 3280151

Change-Id: I48ea134639465d0cc178e524af8d7885d185957d
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index f6577e7..db86740 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -89,6 +89,10 @@
         return mXmlId == R.xml.kbd_qwerty;
     }
 
+    public boolean isSymbolsKeyboard() {
+        return mXmlId == R.xml.kbd_symbols;
+    }
+
     public boolean isPhoneKeyboard() {
         return mMode == MODE_PHONE;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 17d01f8..31d98a6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -51,10 +51,6 @@
         R.layout.input_honeycomb, // DEFAULT_LAYOUT_ID
     };
 
-    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 SubtypeSwitcher mSubtypeSwitcher;
     private SharedPreferences mPrefs;
 
@@ -79,7 +75,14 @@
     private boolean mIsAutoCorrectionActive;
     private boolean mVoiceKeyEnabled;
     private boolean mVoiceButtonOnPrimary;
-    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;
@@ -146,10 +149,9 @@
 
     public void loadKeyboard(int mode, int imeOptions, boolean voiceKeyEnabled,
             boolean voiceButtonOnPrimary) {
-        mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
+        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
         try {
-            loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary,
-                    false);
+            loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary, false);
         } catch (RuntimeException e) {
             Log.w(TAG, e);
             LatinImeLogger.logOnException(mode + "," + imeOptions, e);
@@ -467,19 +469,22 @@
 
     public void onPressSymbol() {
         if (DEBUG_STATE)
-            Log.d(TAG, "onReleaseShift:"
+            Log.d(TAG, "onPressSymbol:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
                     + " symbolKeyState=" + mSymbolKeyState);
         changeKeyboardMode();
         mSymbolKeyState.onPress();
+        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_MOMENTARY;
     }
 
     public void onReleaseSymbol() {
         if (DEBUG_STATE)
-            Log.d(TAG, "onReleaseShift:"
+            Log.d(TAG, "onReleaseSymbol:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
                     + " symbolKeyState=" + mSymbolKeyState);
-        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 (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_CHORDING)
             changeKeyboardMode();
         mSymbolKeyState.onRelease();
     }
@@ -516,13 +521,21 @@
         mInputView.setKeyboard(keyboard);
     }
 
+    public boolean isInMomentaryAutoModeSwitchState() {
+        return mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY;
+    }
+
+    private int getPointerCount() {
+        return mInputView == null ? 0 : mInputView.getPointerCount();
+    }
+
     private void toggleKeyboardMode() {
         loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary,
                 !mIsSymbols);
         if (mIsSymbols) {
-            mSymbolsModeState = SYMBOLS_MODE_STATE_BEGIN;
+            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
         } else {
-            mSymbolsModeState = SYMBOLS_MODE_STATE_NONE;
+            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
         }
     }
 
@@ -531,18 +544,45 @@
     }
 
     /**
-     * Updates state machine to figure out when to automatically switch back to alpha mode.
+     * Updates state machine to figure out when to automatically snap back to the previous mode.
      */
     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 != Keyboard.CODE_SPACE && key != Keyboard.CODE_ENTER && key > 0) {
-                mSymbolsModeState = SYMBOLS_MODE_STATE_SYMBOL;
+        if (DEBUG_STATE)
+            Log.d(TAG, "onKey: code=" + key + " autoModeSwitchState=" + mAutoModeSwitchState
+                    + " pointers=" + getPointerCount());
+        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 == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+                // 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.
+                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 SYMBOLS_MODE_STATE_SYMBOL:
+        case AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN:
+            if (key != Keyboard.CODE_SPACE && key != Keyboard.CODE_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 == Keyboard.CODE_ENTER || key == Keyboard.CODE_SPACE) {
                 changeKeyboardMode();
             }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 2ad414c..47d4d1a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -1308,15 +1308,21 @@
         return pointers.get(id);
     }
 
+    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;
         }
 
@@ -1372,7 +1378,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.
@@ -1387,7 +1392,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/keyboard/ModifierKeyState.java b/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java
index 1bd3d80..f215db8 100644
--- a/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java
@@ -55,6 +55,10 @@
             Log.d(TAG, mName + ".onOtherKeyPressed: " + toString(oldState) + " > " + this);
     }
 
+    public boolean isPressing() {
+        return mState == PRESSING;
+    }
+
     public boolean isReleasing() {
         return mState == RELEASING;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index c96cefd..395783e 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -50,6 +50,7 @@
     private final UIHandler mHandler;
     private final KeyDetector mKeyDetector;
     private KeyboardActionListener mListener;
+    private final KeyboardSwitcher mKeyboardSwitcher;
     private final boolean mHasDistinctMultitouch;
     private final boolean mConfigSlidingKeyInputEnabled;
 
@@ -175,6 +176,7 @@
         mProxy = proxy;
         mHandler = handler;
         mKeyDetector = keyDetector;
+        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
         mKeyState = new KeyState(keyDetector);
         mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
         mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
@@ -318,13 +320,22 @@
         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).mCodes[0]);
                 keyState.onMoveToNewKey(keyIndex, x, y);
                 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.
                 if (mListener != null)
                     mListener.onRelease(oldKey.mCodes[0]);
                 if (mIsAllowedSlidingKeyInput) {
                     resetMultiTap();
+                    if (mListener != null)
+                        mListener.onPress(getKey(keyIndex).mCodes[0]);
                     keyState.onMoveToNewKey(keyIndex, x, y);
                     startLongPressTimer(keyIndex);
                 } else {
@@ -334,7 +345,10 @@
                 }
             }
         } else {
+            // TODO: we should check isMinorMoveDebounce() first.
             if (oldKey != null) {
+                // The pointer has been slid out from the previous key, we must call onRelease() to
+                // notify that the previous key has been released.
                 if (mListener != null)
                     mListener.onRelease(oldKey.mCodes[0]);
                 if (mIsAllowedSlidingKeyInput) {
@@ -449,6 +463,9 @@
         Key key = getKey(keyIndex);
         if (key.mCodes[0] == Keyboard.CODE_SHIFT) {
             mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
+        } else if (mKeyboardSwitcher.isInMomentaryAutoModeSwitchState()) {
+            // We use longer timeout for sliding finger input started from the symbols mode key.
+            mHandler.startLongPressTimer(mLongPressKeyTimeout * 2, keyIndex, this);
         } else {
             mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
         }