Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e287b52 authored by Jean Chalard's avatar Jean Chalard Committed by Android Git Automerger
Browse files

am 8f23c6f7: Merge "Detect cases where rotation messes with initialization"

* commit '8f23c6f7':
  Detect cases where rotation messes with initialization
parents cbd19874 8f23c6f7
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -138,6 +138,9 @@ public final class Constants {
    public static final int SPELL_CHECKER_COORDINATE = -3;
    public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;

    // A hint on how many characters to cache from the TextView. A good value of this is given by
    // how many characters we need to be able to almost always find the caps mode.
    public static final int EDITOR_CONTENTS_CACHE_SIZE = 1024;

    // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
    public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+77 −5
Original line number Diff line number Diff line
@@ -233,6 +233,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        private static final int MSG_RESUME_SUGGESTIONS = 4;
        private static final int MSG_REOPEN_DICTIONARIES = 5;
        private static final int MSG_ON_END_BATCH_INPUT = 6;
        private static final int MSG_RESET_CACHES = 7;

        private static final int ARG1_NOT_GESTURE_INPUT = 0;
        private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -297,6 +298,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
            case MSG_ON_END_BATCH_INPUT:
                latinIme.onEndBatchInputAsyncInternal((SuggestedWords) msg.obj);
                break;
            case MSG_RESET_CACHES:
                latinIme.retryResetCaches(msg.arg1 == 1 /* tryResumeSuggestions */,
                        msg.arg2 /* remainingTries */);
                break;
            }
        }

@@ -313,6 +318,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
            sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
        }

        public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
            removeMessages(MSG_RESET_CACHES);
            sendMessage(obtainMessage(MSG_RESET_CACHES, tryResumeSuggestions ? 1 : 0,
                    remainingTries, null));
        }

        public void cancelUpdateSuggestionStrip() {
            removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
        }
@@ -852,7 +863,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        // span, so we should reset our state unconditionally, even if restarting is true.
        mEnteredText = null;
        resetComposingState(true /* alsoResetLastComposedWord */);
        if (isDifferentTextField) mHandler.postResumeSuggestions();
        mDeleteCount = 0;
        mSpaceState = SPACE_STATE_NONE;
        mRecapitalizeStatus.deactivate();
@@ -871,8 +881,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        }
        mSuggestedWords = SuggestedWords.EMPTY;

        mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart,
                false /* shouldFinishComposition */);
        // Sometimes, while rotating, for some reason the framework tells the app we are not
        // connected to it and that means we can't refresh the cache. In this case, schedule a
        // refresh later.
        if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart,
                false /* shouldFinishComposition */)) {
            // We try resetting the caches up to 5 times before giving up.
            mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
        } else {
            if (isDifferentTextField) mHandler.postResumeSuggestions();
        }

        if (isDifferentTextField) {
            mainKeyboardView.closing();
@@ -899,6 +917,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen

        mLastSelectionStart = editorInfo.initialSelStart;
        mLastSelectionEnd = editorInfo.initialSelEnd;
        // In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
        // so we try using some heuristics to find out about these and fix them.
        tryFixLyingCursorPosition();

        mHandler.cancelUpdateSuggestionStrip();
        mHandler.cancelDoubleSpacePeriodTimer();
@@ -918,6 +939,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
    }

    /**
     * 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
     * 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
     * than it really is.
     */
    private void tryFixLyingCursorPosition() {
        final CharSequence textBeforeCursor =
                mConnection.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
        if (null == textBeforeCursor) {
            mLastSelectionStart = mLastSelectionEnd = NOT_A_CURSOR_POSITION;
        } else {
            final int textLength = textBeforeCursor.length();
            if (textLength > mLastSelectionStart
                    || (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE
                            && mLastSelectionStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
                mLastSelectionStart = textLength;
                // We can't figure out the value of mLastSelectionEnd :(
                // But at least if it's smaller than mLastSelectionStart something is wrong
                if (mLastSelectionStart > mLastSelectionEnd) {
                    mLastSelectionEnd = mLastSelectionStart;
                }
            }
        }
    }

    // Initialization of personalization debug settings. This must be called inside
    // onStartInputView.
    private void initPersonalizationDebugSettings(SettingsValues currentSettingsValues) {
@@ -1072,7 +1122,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                // argument as true. But in all cases where we don't reset the entire input state,
                // we still want to tell the rich input connection about the new cursor position so
                // that it can update its caches.
                mConnection.resetCachesUponCursorMove(newSelStart,
                mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart,
                        false /* shouldFinishComposition */);
            }

@@ -1308,7 +1358,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        } else {
            setSuggestedWords(settingsValues.mSuggestPuncList, false);
        }
        mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
        mConnection.resetCachesUponCursorMoveAndReturnSuccess(newCursorPosition,
                shouldFinishComposition);
    }

    private void resetComposingState(final boolean alsoResetLastComposedWord) {
@@ -2853,6 +2904,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        mHandler.postUpdateSuggestionStrip();
    }

    /**
     * Retry resetting caches in the rich input connection.
     *
     * When the editor can't be accessed we can't reset the caches, so we schedule a retry.
     * This method handles the retry, and re-schedules a new retry if we still can't access.
     * We only retry up to 5 times before giving up.
     *
     * @param tryResumeSuggestions Whether we should resume suggestions or not.
     * @param remainingTries How many times we may try again before giving up.
     */
    private void retryResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
        if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, false)) {
            if (0 < remainingTries) {
                mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
            }
            return;
        }
        tryFixLyingCursorPosition();
        if (tryResumeSuggestions) mHandler.postResumeSuggestions();
    }

    private void revertCommit() {
        final String previousWord = mLastComposedWord.mPrevWord;
        final String originallyTypedWord = mLastComposedWord.mTypedWord;
+49 −12
Original line number Diff line number Diff line
@@ -73,9 +73,6 @@ public final class RichInputConnection {
     * This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
     */
    private final StringBuilder mComposingText = new StringBuilder();
    // A hint on how many characters to cache from the TextView. A good value of this is given by
    // how many characters we need to be able to almost always find the caps mode.
    private static final int DEFAULT_TEXT_CACHE_SIZE = 100;

    private final InputMethodService mParent;
    InputConnection mIC;
@@ -93,7 +90,8 @@ public final class RichInputConnection {
        r.token = 1;
        r.flags = 0;
        final ExtractedText et = mIC.getExtractedText(r, 0);
        final CharSequence beforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
        final CharSequence beforeCursor = getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
                0);
        final StringBuilder internal = new StringBuilder().append(mCommittedTextBeforeComposingText)
                .append(mComposingText);
        if (null == et || null == beforeCursor) return;
@@ -142,19 +140,56 @@ public final class RichInputConnection {
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
    }

    public void resetCachesUponCursorMove(final int newCursorPosition,
    /**
     * Reset the cached text and retrieve it again from the editor.
     *
     * This should be called when the cursor moved. It's possible that we can't connect to
     * the application when doing this; notably, this happens sometimes during rotation, probably
     * because of a race condition in the framework. In this case, we just can't retrieve the
     * data, so we empty the cache and note that we don't know the new cursor position, and we
     * return false so that the caller knows about this and can retry later.
     *
     * @param newCursorPosition The new position of the cursor, as received from the system.
     * @param shouldFinishComposition Whether we should finish the composition in progress.
     * @return true if we were able to connect to the editor successfully, false otherwise. When
     *   this method returns false, the caches could not be correctly refreshed so they were only
     *   reset: the caller should try again later to return to normal operation.
     */
    public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newCursorPosition,
            final boolean shouldFinishComposition) {
        mExpectedCursorPosition = newCursorPosition;
        mComposingText.setLength(0);
        mCommittedTextBeforeComposingText.setLength(0);
        final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
        if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor);
        mIC = mParent.getCurrentInputConnection();
        // Call upon the inputconnection directly since our own method is using the cache, and
        // we want to refresh it.
        final CharSequence textBeforeCursor = null == mIC ? null :
                mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
        if (null == textBeforeCursor) {
            // For some reason the app thinks we are not connected to it. This looks like a
            // framework bug... Fall back to ground state and return false.
            mExpectedCursorPosition = INVALID_CURSOR_POSITION;
            Log.e(TAG, "Unable to connect to the editor to retrieve text... will retry later");
            return false;
        }
        mCommittedTextBeforeComposingText.append(textBeforeCursor);
        final int lengthOfTextBeforeCursor = textBeforeCursor.length();
        if (lengthOfTextBeforeCursor > newCursorPosition
                || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE
                        && newCursorPosition < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
            // newCursorPosition may be lying -- when rotating the device (probably a framework
            // bug). If we have less chars than we asked for, then we know how many chars we have,
            // and if we got more than newCursorPosition says, then we know it was lying. In both
            // cases the length is more reliable
            mExpectedCursorPosition = lengthOfTextBeforeCursor;
        }
        if (null != mIC && shouldFinishComposition) {
            mIC.finishComposingText();
            if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                ResearchLogger.richInputConnection_finishComposingText();
            }
        }
        return true;
    }

    private void checkBatchEdit() {
@@ -233,7 +268,8 @@ public final class RichInputConnection {
        // 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 != mExpectedCursorPosition) {
            final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
            final CharSequence textBeforeCursor = getTextBeforeCursor(
                    Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
            if (!TextUtils.isEmpty(textBeforeCursor)) {
                mCommittedTextBeforeComposingText.append(textBeforeCursor);
            }
@@ -364,7 +400,7 @@ public final class RichInputConnection {
        if (DEBUG_BATCH_NESTING) checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
        final CharSequence textBeforeCursor =
                getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
                getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0);
        mCommittedTextBeforeComposingText.setLength(0);
        if (!TextUtils.isEmpty(textBeforeCursor)) {
            final int indexOfStartOfComposingText =
@@ -406,7 +442,8 @@ public final class RichInputConnection {
        }
        mExpectedCursorPosition = start;
        mCommittedTextBeforeComposingText.setLength(0);
        mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
        mCommittedTextBeforeComposingText.append(
                getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0));
    }

    public void commitCorrection(final CorrectionInfo correctionInfo) {
@@ -525,9 +562,9 @@ public final class RichInputConnection {
        if (mIC == null || sep == null) {
            return null;
        }
        final CharSequence before = mIC.getTextBeforeCursor(1000,
        final CharSequence before = mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
                InputConnection.GET_TEXT_WITH_STYLES);
        final CharSequence after = mIC.getTextAfterCursor(1000,
        final CharSequence after = mIC.getTextAfterCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
                InputConnection.GET_TEXT_WITH_STYLES);
        if (before == null || after == null) {
            return null;