Merge "Fix calculation of more keys keyboard position based on key preview" into jb-dev
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png
index 27d9923..2152017 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png b/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
index 33263b9..7ac82cf 100644
--- a/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
+++ b/java/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_background.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_background.9.png
index a84c19c..1745cec 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_background.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png b/java/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png
index 82513aa..cdfa680 100644
--- a/java/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png
+++ b/java/res/drawable-mdpi/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_background.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_background.9.png
index 16c7610..5ef12a7 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_background.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png
index e020e34..70cef56 100644
--- a/java/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png
+++ b/java/res/drawable-xhdpi/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 667b109..2ea7d83 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -58,7 +58,6 @@
         // These only need to be initialized if the kill switch is off.
         sInstance.initInternal(inputMethod);
         KeyCodeDescriptionMapper.init();
-        AccessibleInputMethodServiceProxy.init(inputMethod);
         AccessibleKeyboardViewProxy.init(inputMethod);
     }
 
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
deleted file mode 100644
index 961176b..0000000
--- a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.accessibility;
-
-import android.content.Context;
-import android.inputmethodservice.InputMethodService;
-import android.media.AudioManager;
-import android.os.Vibrator;
-import android.view.KeyEvent;
-
-public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActionListener {
-    private static final AccessibleInputMethodServiceProxy sInstance =
-            new AccessibleInputMethodServiceProxy();
-
-    /**
-     * Duration of the key click vibration in milliseconds.
-     */
-    private static final long VIBRATE_KEY_CLICK = 50;
-
-    private static final float FX_VOLUME = -1.0f;
-
-    private InputMethodService mInputMethod;
-    private Vibrator mVibrator;
-    private AudioManager mAudioManager;
-
-    public static void init(InputMethodService inputMethod) {
-        sInstance.initInternal(inputMethod);
-    }
-
-    public static AccessibleInputMethodServiceProxy getInstance() {
-        return sInstance;
-    }
-
-    private AccessibleInputMethodServiceProxy() {
-        // Not publicly instantiable.
-    }
-
-    private void initInternal(InputMethodService inputMethod) {
-        mInputMethod = inputMethod;
-        mVibrator = (Vibrator) inputMethod.getSystemService(Context.VIBRATOR_SERVICE);
-        mAudioManager = (AudioManager) inputMethod.getSystemService(Context.AUDIO_SERVICE);
-    }
-
-    /**
-     * Handle flick gestures by mapping them to directional pad keys.
-     */
-    @Override
-    public void onFlickGesture(int direction) {
-        switch (direction) {
-        case FlickGestureDetector.FLICK_LEFT:
-            sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
-            break;
-        case FlickGestureDetector.FLICK_RIGHT:
-            sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
-            break;
-        }
-    }
-
-    /**
-     * Provide haptic feedback and send the specified keyCode to the input
-     * connection as a pair of down/up events.
-     *
-     * @param keyCode
-     */
-    private void sendDownUpKeyEvents(int keyCode) {
-        mVibrator.vibrate(VIBRATE_KEY_CLICK);
-        mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, FX_VOLUME);
-        mInputMethod.sendDownUpKeyEvents(keyCode);
-    }
-}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java
deleted file mode 100644
index 31d17d0..0000000
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.accessibility;
-
-public interface AccessibleKeyboardActionListener {
-    /**
-     * @param direction the direction of the flick gesture, one of
-     *            <ul>
-     *              <li>{@link FlickGestureDetector#FLICK_UP}
-     *              <li>{@link FlickGestureDetector#FLICK_DOWN}
-     *              <li>{@link FlickGestureDetector#FLICK_LEFT}
-     *              <li>{@link FlickGestureDetector#FLICK_RIGHT}
-     *            </ul>
-     */
-    public void onFlickGesture(int direction);
-}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index c85a551..ba814e3 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -17,8 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Paint;
 import android.inputmethodservice.InputMethodService;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.ViewCompat;
@@ -38,16 +36,13 @@
     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
 
     private InputMethodService mInputMethod;
-    private FlickGestureDetector mGestureDetector;
     private LatinKeyboardView mView;
-    private AccessibleKeyboardActionListener mListener;
     private AccessibilityEntityProvider mAccessibilityNodeProvider;
 
     private Key mLastHoverKey = null;
 
     public static void init(InputMethodService inputMethod) {
         sInstance.initInternal(inputMethod);
-        sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance();
     }
 
     public static AccessibleKeyboardViewProxy getInstance() {
@@ -59,14 +54,7 @@
     }
 
     private void initInternal(InputMethodService inputMethod) {
-        final Paint paint = new Paint();
-        paint.setTextAlign(Paint.Align.LEFT);
-        paint.setTextSize(14.0f);
-        paint.setAntiAlias(true);
-        paint.setColor(Color.YELLOW);
-
         mInputMethod = inputMethod;
-        mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
     }
 
     /**
@@ -112,19 +100,6 @@
      * @return {@code true} if the event is handled
      */
     public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
-        if (mGestureDetector.onHoverEvent(event, this, tracker))
-            return true;
-
-        return onHoverEventInternal(event, tracker);
-    }
-
-    /**
-     * Handles touch exploration events when Accessibility is turned on.
-     *
-     * @param event The touch exploration hover event.
-     * @return {@code true} if the event was handled
-     */
-    /* package */boolean onHoverEventInternal(MotionEvent event, PointerTracker tracker) {
         final int x = (int) event.getX();
         final int y = (int) event.getY();
         final Key key = tracker.getKeyOn(x, y);
@@ -214,20 +189,6 @@
         mView.getParent().requestSendAccessibilityEvent(mView, event);
     }
 
-    private class KeyboardFlickGestureDetector extends FlickGestureDetector {
-        public KeyboardFlickGestureDetector(Context context) {
-            super(context);
-        }
-
-        @Override
-        public boolean onFlick(MotionEvent e1, MotionEvent e2, int direction) {
-            if (mListener != null) {
-                mListener.onFlickGesture(direction);
-            }
-            return true;
-        }
-    }
-
     /**
      * Notifies the user of changes in the keyboard shift state.
      */
diff --git a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
deleted file mode 100644
index e8ec376..0000000
--- a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.accessibility;
-
-import android.content.Context;
-import android.os.Message;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-
-import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-
-/**
- * Detects flick gestures within a stream of hover events.
- * <p>
- * A flick gesture is defined as a stream of hover events with the following
- * properties:
- * <ul>
- *   <li>Begins with a {@link MotionEvent#ACTION_HOVER_ENTER} event
- *   <li>Contains any number of {@link MotionEvent#ACTION_HOVER_MOVE}
- *       events
- *   <li>Ends with a {@link MotionEvent#ACTION_HOVER_EXIT} event
- *   <li>Maximum duration of 250 milliseconds
- *   <li>Minimum distance between enter and exit points must be at least equal to
- *       scaled double tap slop (see
- *       {@link ViewConfiguration#getScaledDoubleTapSlop()})
- * </ul>
- * <p>
- * Initial enter events are intercepted and cached until the stream fails to
- * satisfy the constraints defined above, at which point the cached enter event
- * is sent to its source {@link AccessibleKeyboardViewProxy} and subsequent move
- * and exit events are ignored.
- */
-public abstract class FlickGestureDetector {
-    public static final int FLICK_UP = 0;
-    public static final int FLICK_RIGHT = 1;
-    public static final int FLICK_LEFT = 2;
-    public static final int FLICK_DOWN = 3;
-
-    private final FlickHandler mFlickHandler;
-    private final int mFlickRadiusSquare;
-
-    private AccessibleKeyboardViewProxy mCachedView;
-    private PointerTracker mCachedTracker;
-    private MotionEvent mCachedHoverEnter;
-
-    private static class FlickHandler extends StaticInnerHandlerWrapper<FlickGestureDetector> {
-        private static final int MSG_FLICK_TIMEOUT = 1;
-
-        /** The maximum duration of a flick gesture in milliseconds. */
-        private static final int DELAY_FLICK_TIMEOUT = 250;
-
-        public FlickHandler(FlickGestureDetector outerInstance) {
-            super(outerInstance);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            final FlickGestureDetector gestureDetector = getOuterInstance();
-
-            switch (msg.what) {
-            case MSG_FLICK_TIMEOUT:
-                gestureDetector.clearFlick(true);
-            }
-        }
-
-        public void startFlickTimeout() {
-            cancelFlickTimeout();
-            sendEmptyMessageDelayed(MSG_FLICK_TIMEOUT, DELAY_FLICK_TIMEOUT);
-        }
-
-        public void cancelFlickTimeout() {
-            removeMessages(MSG_FLICK_TIMEOUT);
-        }
-    }
-
-    /**
-     * Creates a new flick gesture detector.
-     *
-     * @param context The parent context.
-     */
-    public FlickGestureDetector(Context context) {
-        final int doubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-
-        mFlickHandler = new FlickHandler(this);
-        mFlickRadiusSquare = doubleTapSlop * doubleTapSlop;
-    }
-
-    /**
-     * Processes motion events to detect flick gestures.
-     *
-     * @param event The current event.
-     * @param view The source of the event.
-     * @param tracker A pointer tracker for the event.
-     * @return {@code true} if the event was handled.
-     */
-    public boolean onHoverEvent(MotionEvent event, AccessibleKeyboardViewProxy view,
-            PointerTracker tracker) {
-        // Always cache and consume the first hover event.
-        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
-            mCachedView = view;
-            mCachedTracker = tracker;
-            mCachedHoverEnter = MotionEvent.obtain(event);
-            mFlickHandler.startFlickTimeout();
-            return true;
-        }
-
-        // Stop if the event has already been canceled.
-        if (mCachedHoverEnter == null) {
-            return false;
-        }
-
-        final float distanceSquare = calculateDistanceSquare(mCachedHoverEnter, event);
-
-        switch (event.getAction()) {
-        case MotionEvent.ACTION_HOVER_MOVE:
-            // Consume all valid move events before timeout.
-            return true;
-        case MotionEvent.ACTION_HOVER_EXIT:
-            // Ignore exit events outside the flick radius.
-            if (distanceSquare < mFlickRadiusSquare) {
-                clearFlick(true);
-                return false;
-            } else {
-                return dispatchFlick(mCachedHoverEnter, event);
-            }
-        default:
-            return false;
-        }
-    }
-
-    /**
-     * Clears the cached flick information and optionally forwards the event to
-     * the source view's internal hover event handler.
-     *
-     * @param sendCachedEvent Set to {@code true} to forward the hover event to
-     *            the source view.
-     */
-    private void clearFlick(boolean sendCachedEvent) {
-        mFlickHandler.cancelFlickTimeout();
-
-        if (mCachedHoverEnter != null) {
-            if (sendCachedEvent) {
-                mCachedView.onHoverEventInternal(mCachedHoverEnter, mCachedTracker);
-            }
-            mCachedHoverEnter.recycle();
-            mCachedHoverEnter = null;
-        }
-
-        mCachedTracker = null;
-        mCachedView = null;
-    }
-
-    /**
-     * Computes the direction of a flick gesture and forwards it to
-     * {@link #onFlick(MotionEvent, MotionEvent, int)} for handling.
-     *
-     * @param e1 The {@link MotionEvent#ACTION_HOVER_ENTER} event where the flick started.
-     * @param e2 The {@link MotionEvent#ACTION_HOVER_EXIT} event where the flick ended.
-     * @return {@code true} if the flick event was handled.
-     */
-    private boolean dispatchFlick(MotionEvent e1, MotionEvent e2) {
-        clearFlick(false);
-
-        final float dX = e2.getX() - e1.getX();
-        final float dY = e2.getY() - e1.getY();
-        final int direction;
-
-        if (dY > dX) {
-            if (dY > -dX) {
-                direction = FLICK_DOWN;
-            } else {
-                direction = FLICK_LEFT;
-            }
-        } else {
-            if (dY > -dX) {
-                direction = FLICK_RIGHT;
-            } else {
-                direction = FLICK_UP;
-            }
-        }
-
-        return onFlick(e1, e2, direction);
-    }
-
-    private float calculateDistanceSquare(MotionEvent e1, MotionEvent e2) {
-        final float dX = e2.getX() - e1.getX();
-        final float dY = e2.getY() - e1.getY();
-        return (dX * dX) + (dY * dY);
-    }
-
-    /**
-     * Handles a detected flick gesture.
-     *
-     * @param e1 The {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event
-     *            where the flick started.
-     * @param e2 The {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event
-     *            where the flick ended.
-     * @param direction The direction of the flick event, one of:
-     *            <ul>
-     *              <li>{@link #FLICK_UP}
-     *              <li>{@link #FLICK_DOWN}
-     *              <li>{@link #FLICK_LEFT}
-     *              <li>{@link #FLICK_RIGHT}
-     *            </ul>
-     * @return {@code true} if the flick event was handled.
-     */
-    public abstract boolean onFlick(MotionEvent e1, MotionEvent e2, int direction);
-}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 37d9b6a..c62c3dd 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -606,7 +606,7 @@
                         if (ProductionFlag.IS_EXPERIMENTAL) {
                             ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
                         }
-                        onUpEventInternal(lastX, lastY, eventTime);
+                        onUpEventInternal();
                         onDownEventInternal(x, y, eventTime);
                     } else {
                         mKeyAlreadyProcessed = true;
@@ -646,7 +646,7 @@
             }
             queue.remove(this);
         }
-        onUpEventInternal(x, y, eventTime);
+        onUpEventInternal();
     }
 
     // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
@@ -655,24 +655,15 @@
     public void onPhantomUpEvent(int x, int y, long eventTime) {
         if (DEBUG_EVENT)
             printTouchEvent("onPhntEvent:", x, y, eventTime);
-        onUpEventInternal(x, y, eventTime);
+        onUpEventInternal();
         mKeyAlreadyProcessed = true;
     }
 
-    private void onUpEventInternal(int x, int y, long eventTime) {
+    private void onUpEventInternal() {
         mTimerProxy.cancelKeyTimers();
         mIsInSlidingKeyInput = false;
-        final int keyX, keyY;
-        if (isMajorEnoughMoveToBeOnNewKey(x, y, onMoveKey(x, y))) {
-            keyX = x;
-            keyY = y;
-        } else {
-            // Use previous fixed key coordinates.
-            keyX = mKeyX;
-            keyY = mKeyY;
-        }
-        final Key key = onUpKey(keyX, keyY, eventTime);
-        setReleasedKeyGraphics(key);
+        // Release the last pressed key.
+        setReleasedKeyGraphics(mCurrentKey);
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
             mIsShowingMoreKeysPanel = false;
@@ -680,7 +671,7 @@
         if (mKeyAlreadyProcessed)
             return;
         if (!mIsRepeatableKey) {
-            detectAndSendKey(key, keyX, keyY);
+            detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 9c32f94..229ae2f 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -29,6 +29,7 @@
     final public boolean mInputTypeNoAutoCorrect;
     final public boolean mIsSettingsSuggestionStripOn;
     final public boolean mApplicationSpecifiedCompletionOn;
+    final public int mEditorAction;
 
     public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
         final int inputType = null != editorInfo ? editorInfo.inputType : 0;
@@ -91,6 +92,8 @@
 
             mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode;
         }
+        mEditorAction = (editorInfo == null) ? EditorInfo.IME_ACTION_UNSPECIFIED
+                : editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
     }
 
     @SuppressWarnings("unused")
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7232a48..213c0ac 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1026,13 +1026,25 @@
     }
 
     public boolean getCurrentAutoCapsState() {
+        if (!mSettingsValues.mAutoCap) return false;
+
+        final EditorInfo ei = getCurrentInputEditorInfo();
+        if (ei == null) return false;
+
+        final int inputType = ei.inputType;
+        if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) return true;
+
+        final boolean noNeedToCheckCapsMode = (inputType & (InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
+                | InputType.TYPE_TEXT_FLAG_CAP_WORDS)) == 0;
+        if (noNeedToCheckCapsMode) return false;
+
         final InputConnection ic = getCurrentInputConnection();
-        EditorInfo ei = getCurrentInputEditorInfo();
-        if (mSettingsValues.mAutoCap && ic != null && ei != null
-                && ei.inputType != InputType.TYPE_NULL) {
-            return ic.getCursorCapsMode(ei.inputType) != 0;
-        }
-        return false;
+        if (ic == null) return false;
+        // TODO: This blocking IPC call is heavy. Consider doing this without using IPC calls.
+        // Note: getCursorCapsMode() returns the current capitalization mode that is any
+        // combination of CAP_MODE_CHARACTERS, CAP_MODE_WORDS, and CAP_MODE_SENTENCES. 0 means none
+        // of them.
+        return ic.getCursorCapsMode(inputType) != 0;
     }
 
     // "ic" may be null
@@ -1257,6 +1269,11 @@
             handleLanguageSwitchKey();
             break;
         default:
+            if (primaryCode == Keyboard.CODE_TAB
+                    && mInputAttributes.mEditorAction == EditorInfo.IME_ACTION_NEXT) {
+                performEditorAction(EditorInfo.IME_ACTION_NEXT);
+                break;
+            }
             mSpaceState = SPACE_STATE_NONE;
             if (mSettingsValues.isWordSeparator(primaryCode)) {
                 didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
@@ -1473,22 +1490,23 @@
             sendKeyCodePoint(Keyboard.CODE_SPACE);
         }
 
-        if ((isAlphabet(primaryCode)
+        // NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several
+        // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
+        // thread here.
+        if (!isComposingWord && (isAlphabet(primaryCode)
                 || mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode))
                 && isSuggestionsRequested() && !isCursorTouchingWord()) {
-            if (!isComposingWord) {
-                // Reset entirely the composing state anyway, then start composing a new word unless
-                // the character is a single quote. The idea here is, single quote is not a
-                // separator and it should be treated as a normal character, except in the first
-                // position where it should not start composing a word.
-                isComposingWord = (Keyboard.CODE_SINGLE_QUOTE != primaryCode);
-                // Here we don't need to reset the last composed word. It will be reset
-                // when we commit this one, if we ever do; if on the other hand we backspace
-                // it entirely and resume suggestions on the previous word, we'd like to still
-                // have touch coordinates for it.
-                resetComposingState(false /* alsoResetLastComposedWord */);
-                clearSuggestions();
-            }
+            // Reset entirely the composing state anyway, then start composing a new word unless
+            // the character is a single quote. The idea here is, single quote is not a
+            // separator and it should be treated as a normal character, except in the first
+            // position where it should not start composing a word.
+            isComposingWord = (Keyboard.CODE_SINGLE_QUOTE != primaryCode);
+            // Here we don't need to reset the last composed word. It will be reset
+            // when we commit this one, if we ever do; if on the other hand we backspace
+            // it entirely and resume suggestions on the previous word, we'd like to still
+            // have touch coordinates for it.
+            resetComposingState(false /* alsoResetLastComposedWord */);
+            clearSuggestions();
         }
         if (isComposingWord) {
             mWordComposer.add(