Merge "Fix duplicated apostrophe on AZERTY/tablet"
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6aa43b9..36f4db9 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -62,7 +62,6 @@
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.settings.DebugSettings;
-import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
@@ -203,7 +202,6 @@
 
         private final int mKeyRepeatStartTimeout;
         private final int mKeyRepeatInterval;
-        private final int mLongPressShiftLockTimeout;
         private final int mIgnoreAltCodeKeyTimeout;
         private final int mGestureRecognitionUpdateTime;
 
@@ -215,8 +213,6 @@
                     R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
             mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_keyRepeatInterval, 0);
-            mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
             mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
@@ -280,31 +276,10 @@
         }
 
         @Override
-        public void startLongPressTimer(final PointerTracker tracker) {
+        public void startLongPressTimer(final PointerTracker tracker, final int delay) {
             cancelLongPressTimer();
-            if (tracker == null) {
-                return;
-            }
-            final Key key = tracker.getKey();
-            final int delay;
-            switch (key.mCode) {
-            case Constants.CODE_SHIFT:
-                delay = mLongPressShiftLockTimeout;
-                break;
-            default:
-                final int longpressTimeout =
-                        Settings.getInstance().getCurrent().mKeyLongpressTimeout;
-                if (tracker.isInSlidingKeyInputFromModifier()) {
-                    // We use longer timeout for sliding finger input started from the modifier key.
-                    delay = longpressTimeout * 3;
-                } else {
-                    delay = longpressTimeout;
-                }
-                break;
-            }
-            if (delay > 0) {
-                sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
-            }
+            if (delay <= 0) return;
+            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
         }
 
         @Override
@@ -901,9 +876,12 @@
     }
 
     @Override
-    public void showGestureTrail(final PointerTracker tracker) {
+    public void showGestureTrail(final PointerTracker tracker,
+            final boolean showsFloatingPreviewText) {
         locatePreviewPlacerView();
-        mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        if (showsFloatingPreviewText) {
+            mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        }
         mGestureTrailsPreview.setPreviewPosition(tracker);
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index c7b0964..20fc109 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -34,6 +34,7 @@
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -87,14 +88,14 @@
         public void dismissKeyPreview(PointerTracker tracker);
         public void showSlidingKeyInputPreview(PointerTracker tracker);
         public void dismissSlidingKeyInputPreview();
-        public void showGestureTrail(PointerTracker tracker);
+        public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
     }
 
     public interface TimerProxy {
         public void startTypingStateTimer(Key typedKey);
         public boolean isTypingState();
         public void startKeyRepeatTimer(PointerTracker tracker);
-        public void startLongPressTimer(PointerTracker tracker);
+        public void startLongPressTimer(PointerTracker tracker, int delay);
         public void cancelLongPressTimer();
         public void startDoubleTapShiftKeyTimer();
         public void cancelDoubleTapShiftKeyTimer();
@@ -112,7 +113,7 @@
             @Override
             public void startKeyRepeatTimer(PointerTracker tracker) {}
             @Override
-            public void startLongPressTimer(PointerTracker tracker) {}
+            public void startLongPressTimer(PointerTracker tracker, int delay) {}
             @Override
             public void cancelLongPressTimer() {}
             @Override
@@ -137,6 +138,7 @@
         public final int mTouchNoiseThresholdTime;
         public final int mTouchNoiseThresholdDistance;
         public final int mSuppressKeyPreviewAfterBatchInputDuration;
+        public final int mLongPressShiftLockTimeout;
 
         public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
 
@@ -145,6 +147,7 @@
             mTouchNoiseThresholdTime = 0;
             mTouchNoiseThresholdDistance = 0;
             mSuppressKeyPreviewAfterBatchInputDuration = 0;
+            mLongPressShiftLockTimeout = 0;
         }
 
         public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
@@ -156,6 +159,8 @@
                     R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
             mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
+            mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
         }
     }
 
@@ -327,6 +332,7 @@
     // the more keys panel currently being shown. equals null if no panel is active.
     private MoreKeysPanel mMoreKeysPanel;
 
+    private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
     // true if this pointer is in a sliding key input.
     boolean mIsInSlidingKeyInput;
     // true if this pointer is in a sliding key input from a modifier key,
@@ -602,10 +608,6 @@
         return mIsInSlidingKeyInput;
     }
 
-    public boolean isInSlidingKeyInputFromModifier() {
-        return mIsInSlidingKeyInputFromModifier;
-    }
-
     public Key getKey() {
         return mCurrentKey;
     }
@@ -753,7 +755,7 @@
         return sPointerTrackerQueue.size();
     }
 
-    public boolean isOldestTrackerInQueue() {
+    private boolean isOldestTrackerInQueue() {
         return sPointerTrackerQueue.getOldestElement() == this;
     }
 
@@ -776,7 +778,9 @@
             dismissAllMoreKeysPanels();
         }
         mTimerProxy.cancelLongPressTimer();
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     public void updateBatchInputByTimer(final long eventTime) {
@@ -792,7 +796,9 @@
         if (mIsTrackingForActionDisabled) {
             return;
         }
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     private void updateBatchInput(final long eventTime) {
@@ -833,7 +839,9 @@
         if (mIsTrackingForActionDisabled) {
             return;
         }
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     private void cancelBatchInput() {
@@ -1013,7 +1021,9 @@
             final int translatedY = mMoreKeysPanel.translateY(y);
             mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
             onMoveKey(x, y);
-            mDrawingProxy.showSlidingKeyInputPreview(this);
+            if (mIsInSlidingKeyInputFromModifier) {
+                mDrawingProxy.showSlidingKeyInputPreview(this);
+            }
             return;
         }
         onMoveEventInternal(x, y, eventTime);
@@ -1168,7 +1178,9 @@
                 slideOutFromOldKey(oldKey, x, y);
             }
         }
-        mDrawingProxy.showSlidingKeyInputPreview(this);
+        if (mIsInSlidingKeyInputFromModifier) {
+            mDrawingProxy.showSlidingKeyInputPreview(this);
+        }
     }
 
     public void onUpEvent(final int x, final int y, final long eventTime) {
@@ -1353,7 +1365,22 @@
         // We always need to start the long press timer if the key has its more keys regardless of
         // whether or not we are in the sliding input mode.
         if (mIsInSlidingKeyInput && key.mMoreKeys == null) return;
-        mTimerProxy.startLongPressTimer(this);
+        final int delay;
+        switch (key.mCode) {
+        case Constants.CODE_SHIFT:
+            delay = sParams.mLongPressShiftLockTimeout;
+            break;
+        default:
+            final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
+            if (mIsInSlidingKeyInputFromModifier) {
+                // We use longer timeout for sliding finger input started from the modifier key.
+                delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
+            } else {
+                delay = longpressTimeout;
+            }
+            break;
+        }
+        mTimerProxy.startLongPressTimer(this, delay);
     }
 
     private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
index 9bfddba..c6dd9e1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
@@ -115,9 +115,7 @@
 
     @Override
     public void setPreviewPosition(final PointerTracker tracker) {
-        final boolean needsToUpdateLastPointer =
-                tracker.isOldestTrackerInQueue() && isPreviewEnabled();
-        if (!needsToUpdateLastPointer) {
+        if (!isPreviewEnabled()) {
             return;
         }
         tracker.getLastCoordinates(mLastPointerCoords);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
index 5c9d367..2787ebf 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
@@ -32,7 +32,7 @@
 public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
     private final float mPreviewBodyRadius;
 
-    private boolean mShowSlidingKeyInputPreview;
+    private boolean mShowsSlidingKeyInputPreview;
     private final int[] mPreviewFrom = CoordinateUtils.newInstance();
     private final int[] mPreviewTo = CoordinateUtils.newInstance();
 
@@ -62,7 +62,7 @@
     }
 
     public void dismissSlidingKeyInputPreview() {
-        mShowSlidingKeyInputPreview = false;
+        mShowsSlidingKeyInputPreview = false;
         getDrawingView().invalidate();
     }
 
@@ -72,7 +72,7 @@
      */
     @Override
     public void drawPreview(final Canvas canvas) {
-        if (!isPreviewEnabled() || !mShowSlidingKeyInputPreview) {
+        if (!isPreviewEnabled() || !mShowsSlidingKeyInputPreview) {
             return;
         }
 
@@ -90,13 +90,9 @@
      */
     @Override
     public void setPreviewPosition(final PointerTracker tracker) {
-        if (!tracker.isInSlidingKeyInputFromModifier()) {
-            mShowSlidingKeyInputPreview = false;
-            return;
-        }
         tracker.getDownCoordinates(mPreviewFrom);
         tracker.getLastCoordinates(mPreviewTo);
-        mShowSlidingKeyInputPreview = true;
+        mShowsSlidingKeyInputPreview = true;
         getDrawingView().invalidate();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9366abd..65f0a7a 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -714,7 +714,9 @@
         super.onStartInputView(editorInfo, restarting);
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
-        final SettingsValues currentSettingsValues = mSettings.getCurrent();
+        // If we are starting input in a different text field from before, we'll have to reload
+        // settings, so currentSettingsValues can't be final.
+        SettingsValues currentSettingsValues = mSettings.getCurrent();
 
         if (editorInfo == null) {
             Log.e(TAG, "Null EditorInfo in onStartInputView()");
@@ -808,7 +810,8 @@
         if (isDifferentTextField) {
             mainKeyboardView.closing();
             loadSettings();
-            // TODO: Need to update currentSettingsValues after loadSettings()
+            currentSettingsValues = mSettings.getCurrent();
+
             if (mSuggest != null && currentSettingsValues.mCorrectionEnabled) {
                 mSuggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
             }
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index d07fa47..b69e3f8 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -56,11 +56,14 @@
     private static final int INVALID_CURSOR_POSITION = -1;
 
     /**
-     * This variable contains the value LatinIME thinks the cursor position should be at now.
-     * This is a few steps in advance of what the TextView thinks it is, because TextView will
-     * only know after the IPC calls gets through.
+     * This variable contains an expected value for the cursor position. This is where the
+     * cursor may end up after all the keyboard-triggered updates have passed. We keep this to
+     * compare it to the actual cursor position to guess whether the move was caused by a
+     * keyboard command or not.
+     * It's not really the cursor position: the cursor may not be there yet, and it's also expected 
+     * there be cases where it never actually comes to be there.
      */
-    private int mCurrentCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
+    private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
     /**
      * This contains the committed text immediately preceding the cursor and the composing
      * text if any. It is refreshed when the cursor moves by calling upon the TextView.
@@ -101,16 +104,16 @@
         final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
                 : beforeCursor.subSequence(beforeCursor.length() - actualLength,
                         beforeCursor.length()).toString();
-        if (et.selectionStart != mCurrentCursorPosition
+        if (et.selectionStart != mExpectedCursorPosition
                 || !(reference.equals(internal.toString()))) {
-            final String context = "Expected cursor position = " + mCurrentCursorPosition
+            final String context = "Expected cursor position = " + mExpectedCursorPosition
                     + "\nActual cursor position = " + et.selectionStart
                     + "\nExpected text = " + internal.length() + " " + internal
                     + "\nActual text = " + reference.length() + " " + reference;
             ((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
         } else {
             Log.e(TAG, DebugLogUtils.getStackTrace(2));
-            Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart);
+            Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
         }
     }
 
@@ -141,7 +144,7 @@
 
     public void resetCachesUponCursorMove(final int newCursorPosition,
             final boolean shouldFinishComposition) {
-        mCurrentCursorPosition = newCursorPosition;
+        mExpectedCursorPosition = newCursorPosition;
         mComposingText.setLength(0);
         mCommittedTextBeforeComposingText.setLength(0);
         final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
@@ -166,7 +169,7 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(mComposingText);
-        mCurrentCursorPosition += mComposingText.length();
+        mExpectedCursorPosition += mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.finishComposingText();
@@ -180,7 +183,7 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(text);
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.commitText(text, i);
@@ -193,7 +196,7 @@
     }
 
     public boolean canDeleteCharacters() {
-        return mCurrentCursorPosition > 0;
+        return mExpectedCursorPosition > 0;
     }
 
     /**
@@ -230,7 +233,7 @@
         // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
         // getCapsMode should be updated to be able to return a "not enough info" result so that
         // we can get more context only when needed.
-        if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mCurrentCursorPosition) {
+        if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
             mCommittedTextBeforeComposingText.append(
                     getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
         }
@@ -251,7 +254,7 @@
                 mCommittedTextBeforeComposingText.length() + mComposingText.length();
         // If we have enough characters to satisfy the request, or if we have all characters in
         // the text field, then we can return the cached version right away.
-        if (cachedLength >= n || cachedLength >= mCurrentCursorPosition) {
+        if (cachedLength >= n || cachedLength >= mExpectedCursorPosition) {
             final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
             s.append(mComposingText);
             if (s.length() > n) {
@@ -284,10 +287,10 @@
                     + remainingChars, 0);
             mCommittedTextBeforeComposingText.setLength(len);
         }
-        if (mCurrentCursorPosition > beforeLength) {
-            mCurrentCursorPosition -= beforeLength;
+        if (mExpectedCursorPosition > beforeLength) {
+            mExpectedCursorPosition -= beforeLength;
         } else {
-            mCurrentCursorPosition = 0;
+            mExpectedCursorPosition = 0;
         }
         if (null != mIC) {
             mIC.deleteSurroundingText(beforeLength, afterLength);
@@ -321,7 +324,7 @@
             switch (keyEvent.getKeyCode()) {
             case KeyEvent.KEYCODE_ENTER:
                 mCommittedTextBeforeComposingText.append("\n");
-                mCurrentCursorPosition += 1;
+                mExpectedCursorPosition += 1;
                 break;
             case KeyEvent.KEYCODE_DEL:
                 if (0 == mComposingText.length()) {
@@ -333,18 +336,18 @@
                 } else {
                     mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
                 }
-                if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
+                if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
                 break;
             case KeyEvent.KEYCODE_UNKNOWN:
                 if (null != keyEvent.getCharacters()) {
                     mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
-                    mCurrentCursorPosition += keyEvent.getCharacters().length();
+                    mExpectedCursorPosition += keyEvent.getCharacters().length();
                 }
                 break;
             default:
                 final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
                 mCommittedTextBeforeComposingText.append(text);
-                mCurrentCursorPosition += text.length();
+                mExpectedCursorPosition += text.length();
                 break;
             }
         }
@@ -378,7 +381,7 @@
     public void setComposingText(final CharSequence text, final int newCursorPosition) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         mComposingText.append(text);
         // TODO: support values of i != 1. At this time, this is never called with i != 1.
@@ -400,7 +403,7 @@
                 ResearchLogger.richInputConnection_setSelection(start, end);
             }
         }
-        mCurrentCursorPosition = start;
+        mExpectedCursorPosition = start;
         mCommittedTextBeforeComposingText.setLength(0);
         mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
     }
@@ -423,7 +426,7 @@
         // text should never be null, but just in case, it's better to insert nothing than to crash
         if (null == text) text = "";
         mCommittedTextBeforeComposingText.append(text);
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.commitCompletion(completionInfo);
@@ -705,14 +708,14 @@
      */
     public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) {
         // If this is an update that arrives at our expected position, it's a belated update.
-        if (newSelStart == mCurrentCursorPosition) return true;
+        if (newSelStart == mExpectedCursorPosition) return true;
         // If this is an update that moves the cursor from our expected position, it must be
         // an explicit move.
-        if (oldSelStart == mCurrentCursorPosition) return false;
+        if (oldSelStart == mExpectedCursorPosition) return false;
         // The following returns true if newSelStart is between oldSelStart and
         // mCurrentCursorPosition. We assume that if the updated position is between the old
         // position and the expected position, then it must be a belated update.
-        return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0;
+        return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
     }
 
     /**