Merge "Enable preliminary Sinhara keyboard for dogfood"
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5aae010..099d485 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -247,7 +247,6 @@
             case MSG_RESUME_SUGGESTIONS:
                 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
                         latinIme.mSettings.getCurrent(),
-                        msg.arg1 == ARG1_TRUE /* shouldIncludeResumedWordInSuggestions */,
                         latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId());
                 break;
             case MSG_REOPEN_DICTIONARIES:
@@ -288,8 +287,7 @@
             sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
         }
 
-        public void postResumeSuggestions(final boolean shouldIncludeResumedWordInSuggestions,
-                final boolean shouldDelay) {
+        public void postResumeSuggestions(final boolean shouldDelay) {
             final LatinIME latinIme = getOwnerInstance();
             if (latinIme == null) {
                 return;
@@ -299,13 +297,10 @@
             }
             removeMessages(MSG_RESUME_SUGGESTIONS);
             if (shouldDelay) {
-                sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS,
-                        shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
-                        0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
+                sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS),
+                        mDelayInMillisecondsToUpdateSuggestions);
             } else {
-                sendMessage(obtainMessage(MSG_RESUME_SUGGESTIONS,
-                        shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
-                        0 /* ignored */));
+                sendMessage(obtainMessage(MSG_RESUME_SUGGESTIONS));
             }
         }
 
@@ -622,8 +617,7 @@
         }
         if (mHandler.hasPendingWaitForDictionaryLoad()) {
             mHandler.cancelWaitForDictionaryLoad();
-            mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
-                    false /* shouldDelay */);
+            mHandler.postResumeSuggestions(false /* shouldDelay */);
         }
     }
 
@@ -926,11 +920,12 @@
                 // mLastSelection{Start,End} are reset later in this method, no need to do it here
                 needToCallLoadKeyboardLater = true;
             } else {
-                // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best
-                // effort to work around this bug.
+                // When rotating, and when input is starting again in a field from where the focus
+                // didn't move (the keyboard having been closed with the back key),
+                // initialSelStart and initialSelEnd sometimes are lying. Make a best effort to
+                // work around this bug.
                 mInputLogic.mConnection.tryFixLyingCursorPosition();
-                mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
-                        true /* shouldDelay */);
+                mHandler.postResumeSuggestions(true /* shouldDelay */);
                 needToCallLoadKeyboardLater = false;
             }
         } else {
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 7507061..a7ea2a1 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -849,8 +849,9 @@
 
     /**
      * Try to get the text from the editor to expose lies the framework may have been
-     * telling us. Concretely, when the device rotates, the frameworks tells us about where the
-     * cursor used to be initially in the editor at the time it first received the focus; this
+     * telling us. Concretely, when the device rotates and when the keyboard reopens in the same
+     * text field after having been closed with the back key, the frameworks tells us about where
+     * the cursor used to be initially in the editor at the time it first received the focus; this
      * may be completely different from the place it is upon rotation. Since we don't have any
      * means to get the real value, try at least to ask the text view for some characters and
      * detect the most damaging cases: when the cursor position is declared to be much smaller
@@ -859,7 +860,20 @@
     public void tryFixLyingCursorPosition() {
         final CharSequence textBeforeCursor = getTextBeforeCursor(
                 Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
-        if (null == textBeforeCursor) {
+        final CharSequence selectedText = mIC.getSelectedText(0 /* flags */);
+        if (null == textBeforeCursor ||
+                (!TextUtils.isEmpty(selectedText) && mExpectedSelEnd == mExpectedSelStart)) {
+            // If textBeforeCursor is null, we have no idea what kind of text field we have or if
+            // thinking about the "cursor position" actually makes any sense. In this case we
+            // remember a meaningless cursor position. Contrast this with an empty string, which is
+            // valid and should mean the cursor is at the start of the text.
+            // Also, if we expect we don't have a selection but we DO have non-empty selected text,
+            // then the framework lied to us about the cursor position. In this case, we should just
+            // revert to the most basic behavior possible for the next action (backspace in
+            // particular comes to mind), so we remember a meaningless cursor position which should
+            // result in degraded behavior from the next input.
+            // Interestingly, in either case, chances are any action the user takes next will result
+            // in a call to onUpdateSelection, which should set things right.
             mExpectedSelStart = mExpectedSelEnd = Constants.NOT_A_CURSOR_POSITION;
         } else {
             final int textLength = textBeforeCursor.length();
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index d7e1eba..b4a1c3e 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -435,8 +435,7 @@
         // removed.
         mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
         // We moved the cursor. If we are touching a word, we need to resume suggestion.
-        mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
-                true /* shouldDelay */);
+        mLatinIME.mHandler.postResumeSuggestions(true /* shouldDelay */);
         // Stop the last recapitalization, if started.
         mRecapitalizeStatus.stop();
         return true;
@@ -1127,19 +1126,21 @@
                 StatsUtils.onBackspaceSelectedText(numCharsDeleted);
             } else {
                 // There is no selection, just delete one character.
-                if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
-                    // This should never happen.
-                    Log.e(TAG, "Backspace when we don't know the selection position");
-                }
-                if (inputTransaction.mSettingsValues.isBeforeJellyBean() ||
-                        inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()) {
-                    // There are two possible reasons to send a key event: either the field has
+                if (inputTransaction.mSettingsValues.isBeforeJellyBean()
+                        || inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()
+                        || Constants.NOT_A_CURSOR_POSITION
+                                == mConnection.getExpectedSelectionEnd()) {
+                    // There are three possible reasons to send a key event: either the field has
                     // type TYPE_NULL, in which case the keyboard should send events, or we are
-                    // running in backward compatibility mode. Before Jelly bean, the keyboard
-                    // would simulate a hardware keyboard event on pressing enter or delete. This
-                    // is bad for many reasons (there are race conditions with commits) but some
-                    // applications are relying on this behavior so we continue to support it for
-                    // older apps, so we retain this behavior if the app has target SDK < JellyBean.
+                    // running in backward compatibility mode, or we don't know the cursor position.
+                    // Before Jelly bean, the keyboard would simulate a hardware keyboard event on
+                    // pressing enter or delete. This is bad for many reasons (there are race
+                    // conditions with commits) but some applications are relying on this behavior
+                    // so we continue to support it for older apps, so we retain this behavior if
+                    // the app has target SDK < JellyBean.
+                    // As for the case where we don't know the cursor position, it can happen
+                    // because of bugs in the framework. But the framework should know, so the next
+                    // best thing is to leave it to whatever it thinks is best.
                     sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
                     int totalDeletedLength = 1;
                     if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
@@ -1184,7 +1185,7 @@
                     && !mConnection.isCursorFollowedByWordCharacter(
                             inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
                 restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
-                        true /* shouldIncludeResumedWordInSuggestions */, currentKeyboardScriptId);
+                        currentKeyboardScriptId);
             }
         }
     }
@@ -1440,12 +1441,10 @@
      * do nothing.
      *
      * @param settingsValues the current values of the settings.
-     * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume
      *   suggestions in the suggestion list.
      */
     // TODO: make this private.
     public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
-            final boolean shouldIncludeResumedWordInSuggestions,
             // TODO: remove this argument, put it into settingsValues
             final int currentKeyboardScriptId) {
         // HACK: We may want to special-case some apps that exhibit bad behavior in case of
@@ -1493,13 +1492,6 @@
         if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
         final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
         final String typedWord = range.mWord.toString();
-        if (shouldIncludeResumedWordInSuggestions) {
-            suggestions.add(new SuggestedWordInfo(typedWord,
-                    SuggestedWords.MAX_SUGGESTIONS + 1,
-                    SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
-                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
-                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
-        }
         if (!isResumableWord(settingsValues, typedWord)) {
             mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
             return;
@@ -1532,7 +1524,7 @@
         mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug();
         mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
                 expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
-        if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
+        if (suggestions.size() <= 0) {
             // If there weren't any suggestion spans on this word, suggestions#size() will be 1
             // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
             // have no useful suggestions, so we will try to compute some for it instead.
@@ -1542,8 +1534,7 @@
                         public void onGetSuggestedWords(
                                 final SuggestedWords suggestedWordsIncludingTypedWord) {
                             final SuggestedWords suggestedWords;
-                            if (suggestedWordsIncludingTypedWord.size() > 1
-                                    && !shouldIncludeResumedWordInSuggestions) {
+                            if (suggestedWordsIncludingTypedWord.size() > 1) {
                                 // We were able to compute new suggestions for this word.
                                 // Remove the typed word, since we don't want to display it in this
                                 // case. The #getSuggestedWordsExcludingTypedWordForRecorrection()
@@ -1649,8 +1640,10 @@
                 }
             }
             // Add the suggestion list to the list of suggestions.
-            textToCommit.setSpan(new SuggestionSpan(inputTransaction.mSettingsValues.mLocale,
-                    suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
+            textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */,
+                    inputTransaction.mSettingsValues.mLocale,
+                    suggestions.toArray(new String[suggestions.size()]), 0 /* flags */,
+                    null /* notificationTargetClass */),
                     0 /* start */, lastCharIndex /* end */, 0 /* flags */);
         }
 
@@ -2194,10 +2187,7 @@
         }
         mConnection.tryFixLyingCursorPosition();
         if (tryResumeSuggestions) {
-            // This is triggered when starting input anew, so we want to include the resumed
-            // word in suggestions.
-            handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
-                    true /* shouldDelay */);
+            handler.postResumeSuggestions(true /* shouldDelay */);
         }
         return true;
     }