diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index f6577e747..db86740c3 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -89,6 +89,10 @@ public class KeyboardId { 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 17d01f89f..31d98a676 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -51,10 +51,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha 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 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha 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 class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha 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 class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha 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 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha 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 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } /** - * 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 2ad414c90..47d4d1ade 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -1308,15 +1308,21 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { 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 1bd3d8040..f215db876 100644 --- a/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java +++ b/java/src/com/android/inputmethod/keyboard/ModifierKeyState.java @@ -55,6 +55,10 @@ public class ModifierKeyState { 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 c96cefd2b..395783e86 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -50,6 +50,7 @@ public class PointerTracker { 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 @@ public class PointerTracker { 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 @@ public class PointerTracker { 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 @@ public class PointerTracker { } } } 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 @@ public class PointerTracker { 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); }