Merge "Straighten out database cursors behavior."
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d922ef6..75ba24d7 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -171,10 +171,8 @@
 
         public void onCreate() {
             final Resources res = getOwnerInstance().getResources();
-            mDelayUpdateSuggestions =
-                    res.getInteger(R.integer.config_delay_update_suggestions);
-            mDelayUpdateShiftState =
-                    res.getInteger(R.integer.config_delay_update_shift_state);
+            mDelayUpdateSuggestions = res.getInteger(R.integer.config_delay_update_suggestions);
+            mDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state);
             mDoubleSpacePeriodTimeout =
                     res.getInteger(R.integer.config_double_space_period_timeout);
         }
@@ -339,12 +337,15 @@
 
         private void executePendingImsCallback(final LatinIME latinIme, final EditorInfo editorInfo,
                 boolean restarting) {
-            if (mHasPendingFinishInputView)
+            if (mHasPendingFinishInputView) {
                 latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
-            if (mHasPendingFinishInput)
+            }
+            if (mHasPendingFinishInput) {
                 latinIme.onFinishInputInternal();
-            if (mHasPendingStartInput)
+            }
+            if (mHasPendingStartInput) {
                 latinIme.onStartInputInternal(editorInfo, restarting);
+            }
             resetPendingImsCallback();
         }
 
@@ -579,9 +580,8 @@
                 (mInputLogic.mSuggest == null) ? null : mInputLogic.mSuggest.mDictionaryFacilitator;
         // Creates new dictionary facilitator for the new locale.
         final DictionaryFacilitatorForSuggest dictionaryFacilitator =
-                new DictionaryFacilitatorForSuggest(this /* context */, locale,
-                        settingsValues, this /* DictionaryInitializationListener */,
-                        oldDictionaryFacilitator);
+                new DictionaryFacilitatorForSuggest(this /* context */, locale, settingsValues,
+                        this /* DictionaryInitializationListener */, oldDictionaryFacilitator);
         final Suggest newSuggest = new Suggest(locale, dictionaryFacilitator);
         if (settingsValues.mCorrectionEnabled) {
             newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
@@ -658,7 +658,7 @@
                 .findViewById(android.R.id.extractArea);
         mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing);
         mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
-        if (mSuggestionStripView != null) {
+        if (hasSuggestionStripView()) {
             mSuggestionStripView.setListener(this, view);
         }
         if (LatinImeLogger.sVISUALDEBUG) {
@@ -738,13 +738,11 @@
             ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs);
         }
         if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
-            Log.w(TAG, "Deprecated private IME option specified: "
-                    + editorInfo.privateImeOptions);
+            Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
             Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
         }
         if (InputAttributes.inPrivateImeOptions(getPackageName(), FORCE_ASCII, editorInfo)) {
-            Log.w(TAG, "Deprecated private IME option specified: "
-                    + editorInfo.privateImeOptions);
+            Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
             Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
         }
 
@@ -890,12 +888,9 @@
         super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
                 composingSpanStart, composingSpanEnd);
         if (DEBUG) {
-            Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
-                    + ", ose=" + oldSelEnd
-                    + ", nss=" + newSelStart
-                    + ", nse=" + newSelEnd
-                    + ", cs=" + composingSpanStart
-                    + ", ce=" + composingSpanEnd);
+            Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd
+                    + ", nss=" + newSelStart + ", nse=" + newSelEnd
+                    + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_onUpdateSelection(oldSelStart, oldSelEnd,
@@ -1009,7 +1004,7 @@
     private void setSuggestionStripShownInternal(final boolean isSuggestionStripVisible,
             final boolean needsInputViewShown) {
         // TODO: Modify this if we support suggestions with hard keyboard
-        if (!onEvaluateInputViewShown() || null == mSuggestionStripView) {
+        if (!onEvaluateInputViewShown() || !hasSuggestionStripView()) {
             return;
         }
         final boolean inputViewShown = mKeyboardSwitcher.isShowingMainKeyboardOrEmojiPalettes();
@@ -1053,7 +1048,7 @@
     public void onComputeInsets(final InputMethodService.Insets outInsets) {
         super.onComputeInsets(outInsets);
         final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
-        if (visibleKeyboardView == null || mSuggestionStripView == null) {
+        if (visibleKeyboardView == null || !hasSuggestionStripView()) {
             return;
         }
         final int adjustedBackingHeight = getAdjustedBackingViewHeight();
@@ -1192,7 +1187,9 @@
     }
 
     public void displaySettingsDialog() {
-        if (isShowingOptionDialog()) return;
+        if (isShowingOptionDialog()) {
+            return;
+        }
         showSubtypeSelectorAndSettings();
     }
 
@@ -1252,8 +1249,8 @@
             mSubtypeSwitcher.switchToShortcutIME(this);
             // Still call the *#onCodeInput methods for readability.
         }
-        mInputLogic.onCodeInput(codeToSend, keyX, keyY, mSettings.getCurrent(),
-                mHandler, mKeyboardSwitcher);
+        mInputLogic.onCodeInput(codeToSend, keyX, keyY, mSettings.getCurrent(), mHandler,
+                mKeyboardSwitcher);
         mKeyboardSwitcher.onCodeInput(codePoint);
     }
 
@@ -1310,21 +1307,15 @@
         // Nothing to do so far.
     }
 
-    // TODO: remove this, read this directly from mInputLogic or something in the tests
-    @UsedForTesting
-    public boolean isShowingPunctuationList() {
-        return mInputLogic.isShowingPunctuationList(mSettings.getCurrent());
-    }
-
     // TODO[IL]: Define a clear interface for this
     public boolean isSuggestionStripVisible() {
-        final SettingsValues currentSettings = mSettings.getCurrent();
-        if (mSuggestionStripView == null) {
+        if (!hasSuggestionStripView()) {
             return false;
         }
         if (mSuggestionStripView.isShowingAddToDictionaryHint()) {
             return true;
         }
+        final SettingsValues currentSettings = mSettings.getCurrent();
         if (null == currentSettings) {
             return false;
         }
@@ -1353,16 +1344,17 @@
 
     @Override
     public void dismissAddToDictionaryHint() {
-        if (null != mSuggestionStripView) {
-            mSuggestionStripView.dismissAddToDictionaryHint();
+        if (!hasSuggestionStripView()) {
+            return;
         }
+        mSuggestionStripView.dismissAddToDictionaryHint();
     }
 
     // TODO[IL]: Define a clear interface for this
     public void setSuggestedWords(final SuggestedWords suggestedWords,
             final boolean isSuggestionStripVisible, final boolean needsInputViewShown) {
         mInputLogic.setSuggestedWords(suggestedWords);
-        if (mSuggestionStripView == null) {
+        if (!hasSuggestionStripView()) {
             return;
         }
         final SettingsValues currentSettings = mSettings.getCurrent();
@@ -1419,9 +1411,9 @@
         }
         suggest.getSuggestedWords(mInputLogic.mWordComposer,
                 mInputLogic.mWordComposer.getPreviousWordForSuggestion(),
-                keyboard.getProximityInfo(),
-                currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
-                additionalFeaturesOptions, sessionId, sequenceNumber, callback);
+                keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive,
+                currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId,
+                sequenceNumber, callback);
     }
 
     // TODO[IL]: Move this to InputLogic
@@ -1435,7 +1427,7 @@
         // the "add to dictionary" hint, we need to revert to suggestions - although it is unclear
         // how we can come here if it's displayed.
         if (suggestedWords.size() > 1 || typedWord.length() <= 1
-                || null == mSuggestionStripView || isShowingAddToDictionaryHint()) {
+                || !hasSuggestionStripView() || isShowingAddToDictionaryHint()) {
             return suggestedWords;
         } else {
             final SuggestedWords punctuationList =
@@ -1445,10 +1437,8 @@
             final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
                     SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
             return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
-                    false /* typedWordValid */,
-                    false /* hasAutoCorrectionCandidate */,
-                    true /* isObsoleteSuggestions */,
-                    false /* isPrediction */);
+                    false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
+                    true /* isObsoleteSuggestions */, false /* isPrediction */);
         }
     }
 
@@ -1487,7 +1477,9 @@
 
     @Override
     public void showAddToDictionaryHint(final String word) {
-        if (null == mSuggestionStripView) return;
+        if (!hasSuggestionStripView()) {
+            return;
+        }
         mSuggestionStripView.showAddToDictionaryHint(word);
     }
 
@@ -1692,7 +1684,7 @@
 
     // TODO: can this be removed somehow without breaking the tests?
     @UsedForTesting
-    /* package for test */ SuggestedWords getSuggestedWords() {
+    /* package for test */ SuggestedWords getSuggestedWordsForTest() {
         // You may not use this method for anything else than debug
         return DEBUG ? mInputLogic.mSuggestedWords : null;
     }
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index eb1899c..ebad9bc 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -230,6 +230,9 @@
     public void finishComposingText() {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
+        // TODO: this is not correct! The cursor is not necessarily after the composing text.
+        // In the practice right now this is only called when input ends so it will be reset so
+        // it works, but it's wrong and should be fixed.
         mCommittedTextBeforeComposingText.append(mComposingText);
         mComposingText.setLength(0);
         if (null != mIC) {
@@ -244,6 +247,9 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(text);
+        // TODO: the following is exceedingly error-prone. Right now when the cursor is in the
+        // middle of the composing word mComposingText only holds the part of the composing text
+        // that is before the cursor, so this actually works, but it's terribly confusing. Fix this.
         mExpectedSelStart += text.length() - mComposingText.length();
         mExpectedSelEnd = mExpectedSelStart;
         mComposingText.setLength(0);
@@ -347,6 +353,9 @@
 
     public void deleteSurroundingText(final int beforeLength, final int afterLength) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
+        // TODO: the following is incorrect if the cursor is not immediately after the composition.
+        // Right now we never come here in this case because we reset the composing state before we
+        // come here in this case, but we need to fix this.
         final int remainingChars = mComposingText.length() - beforeLength;
         if (remainingChars >= 0) {
             mComposingText.setLength(remainingChars);
@@ -447,8 +456,12 @@
                 getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0);
         mCommittedTextBeforeComposingText.setLength(0);
         if (!TextUtils.isEmpty(textBeforeCursor)) {
+            // The cursor is not necessarily at the end of the composing text, but we have its
+            // position in mExpectedSelStart and mExpectedSelEnd. In this case we want the start
+            // of the text, so we should use mExpectedSelStart. In other words, the composing
+            // text starts (mExpectedSelStart - start) characters before the end of textBeforeCursor
             final int indexOfStartOfComposingText =
-                    Math.max(textBeforeCursor.length() - (end - start), 0);
+                    Math.max(textBeforeCursor.length() - (mExpectedSelStart - start), 0);
             mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText,
                     textBeforeCursor.length()));
             mCommittedTextBeforeComposingText.append(
@@ -544,6 +557,9 @@
             final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
             final String reference = prev.length() <= checkLength ? prev.toString()
                     : prev.subSequence(prev.length() - checkLength, prev.length()).toString();
+            // TODO: right now the following works because mComposingText holds the part of the
+            // composing text that is before the cursor, but this is very confusing. We should
+            // fix it.
             final StringBuilder internal = new StringBuilder()
                     .append(mCommittedTextBeforeComposingText).append(mComposingText);
             if (internal.length() > checkLength) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 9bf9d1f..3fc2cf8 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -198,7 +198,7 @@
         final SuggestedWords suggestedWords = mSuggestedWords;
         final String suggestion = suggestionInfo.mWord;
         // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
-        if (suggestion.length() == 1 && isShowingPunctuationList(settingsValues)) {
+        if (suggestion.length() == 1 && suggestedWords.isPunctuationSuggestions()) {
             // Word separators are suggested before the user inputs something.
             // So, LatinImeLogger logs "" as a user's input.
             LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
@@ -821,7 +821,7 @@
                 if (maybeDoubleSpacePeriod(settingsValues, handler)) {
                     keyboardSwitcher.updateShiftState();
                     mSpaceState = SpaceState.DOUBLE;
-                } else if (!isShowingPunctuationList(settingsValues)) {
+                } else if (!mSuggestedWords.isPunctuationSuggestions()) {
                     mSpaceState = SpaceState.WEAK;
                 }
             }
@@ -1456,15 +1456,6 @@
     }
 
     /**
-     * Find out if the punctuation list is shown in the suggestion strip.
-     * @return whether the current suggestions are the punctuation list.
-     */
-    // TODO: make this private. It's used through LatinIME for tests.
-    public boolean isShowingPunctuationList(final SettingsValues settingsValues) {
-        return settingsValues.mSpacingAndPunctuations.mSuggestPuncList == mSuggestedWords;
-    }
-
-    /**
      * Factor in auto-caps and manual caps and compute the current caps mode.
      * @param settingsValues the current settings values.
      * @param keyboardShiftMode the current shift mode of the keyboard. See
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 2c23d74..444b8fa 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -357,7 +357,7 @@
         sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
         runMessages();
         // Test the first prediction is displayed
-        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWords();
+        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
         assertEquals("predictions after space", "Obama",
                 suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null);
     }
@@ -371,7 +371,7 @@
         sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
         runMessages();
         // Test the first prediction is displayed
-        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWords();
+        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
         assertEquals("predictions after manual pick", "Obama",
                 suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null);
     }
@@ -382,7 +382,44 @@
         sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
         runMessages();
         // Test the first prediction is not displayed
-        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWords();
+        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
         assertEquals("no prediction after period", 0, suggestedWords.size());
     }
+
+    public void testPredictionsAfterRecorrection() {
+        final String PREFIX = "A ";
+        final String WORD_TO_TYPE = "Barack";
+        final String FIRST_NON_TYPED_SUGGESTION = "Barrack";
+        final int endOfPrefix = PREFIX.length();
+        final int endOfWord = endOfPrefix + WORD_TO_TYPE.length();
+        final int endOfSuggestion = endOfPrefix + FIRST_NON_TYPED_SUGGESTION.length();
+        final int indexForManualCursor = endOfPrefix + 3; // +3 because it's after "Bar" in "Barack"
+        type(PREFIX);
+        mLatinIME.onUpdateSelection(0, 0, endOfPrefix, endOfPrefix, -1, -1);
+        type(WORD_TO_TYPE);
+        pickSuggestionManually(1, FIRST_NON_TYPED_SUGGESTION);
+        mLatinIME.onUpdateSelection(endOfPrefix, endOfPrefix, endOfSuggestion, endOfSuggestion,
+                -1, -1);
+        runMessages();
+        type(" ");
+        mLatinIME.onUpdateSelection(endOfSuggestion, endOfSuggestion,
+                endOfSuggestion + 1, endOfSuggestion + 1, -1, -1);
+        sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
+        runMessages();
+        // Simulate a manual cursor move
+        mInputConnection.setSelection(indexForManualCursor, indexForManualCursor);
+        mLatinIME.onUpdateSelection(endOfSuggestion + 1, endOfSuggestion + 1,
+                indexForManualCursor, indexForManualCursor, -1, -1);
+        sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
+        runMessages();
+        pickSuggestionManually(0, WORD_TO_TYPE);
+        mLatinIME.onUpdateSelection(indexForManualCursor, indexForManualCursor,
+                endOfWord, endOfWord, -1, -1);
+        sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
+        runMessages();
+        // Test the first prediction is displayed
+        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWords();
+        assertEquals("predictions after recorrection", "Obama",
+                suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null);
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java
index 89021b4..e38ba72 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsLanguageWithoutSpaces.java
@@ -99,7 +99,7 @@
         assertEquals("predictions in lang without spaces", "Barack",
                 mEditText.getText().toString());
         // Test the first prediction is displayed
-        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWords();
+        final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
         assertEquals("predictions in lang without spaces", "Obama",
                 suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null);
     }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
index a474c6a..1257ae2 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
@@ -60,7 +60,7 @@
             sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
             runMessages();
             assertTrue("type word then type space should display punctuation strip",
-                    mLatinIME.isShowingPunctuationList());
+                    mLatinIME.getSuggestedWordsForTest().isPunctuationSuggestions());
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             assertEquals("type word then type space then punctuation from strip twice for French",
@@ -84,7 +84,7 @@
             type(WORD_TO_TYPE);
             sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
             runMessages();
-            final SuggestedWords suggestedWords = mLatinIME.getSuggestedWords();
+            final SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
             assertEquals("type word then type space yields predictions for French",
                     EXPECTED_RESULT, suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null);
         } finally {
diff --git a/tests/src/com/android/inputmethod/latin/PunctuationTests.java b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
index 556af09..c253e64 100644
--- a/tests/src/com/android/inputmethod/latin/PunctuationTests.java
+++ b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
@@ -41,7 +41,7 @@
             sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
             runMessages();
             assertTrue("type word then type space should display punctuation strip",
-                    mLatinIME.isShowingPunctuationList());
+                    mLatinIME.getSuggestedWordsForTest().isPunctuationSuggestions());
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             assertEquals("type word then type space then punctuation from strip twice",
@@ -156,7 +156,7 @@
         type(WORD_TO_TYPE);
         assertEquals("auto-correction with single quote inside. ID = "
                 + Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID)
-                + " ; Suggestions = " + mLatinIME.getSuggestedWords(),
+                + " ; Suggestions = " + mLatinIME.getSuggestedWordsForTest(),
                 EXPECTED_RESULT, mEditText.getText().toString());
     }
 
@@ -166,7 +166,7 @@
         type(WORD_TO_TYPE);
         assertEquals("auto-correction with single quotes around. ID = "
                 + Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID)
-                + " ; Suggestions = " + mLatinIME.getSuggestedWords(),
+                + " ; Suggestions = " + mLatinIME.getSuggestedWordsForTest(),
                 EXPECTED_RESULT, mEditText.getText().toString());
     }