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

Commit 18fc3bf4 authored by Jean Chalard's avatar Jean Chalard Committed by Android (Google) Code Review
Browse files

Merge "Make Latin IME aware of its surrounding text." into jb-mr1-dev

parents 57e95111 28d765ed
Loading
Loading
Loading
Loading
+25 −8
Original line number Diff line number Diff line
@@ -733,6 +733,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
                mCurrentSettings.mGestureFloatingPreviewTextEnabled);

        mConnection.resetCachesUponCursorMove(mLastSelectionStart);

        if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
    }

@@ -839,7 +841,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
            mSpaceState = SPACE_STATE_NONE;

            if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) {
                resetEntireInputState();
                resetEntireInputState(newSelStart);
            }

            mHandler.postUpdateShiftState();
@@ -1043,14 +1045,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen

    // This will reset the whole input state to the starting state. It will clear
    // the composing word, reset the last composed word, tell the inputconnection about it.
    private void resetEntireInputState() {
    private void resetEntireInputState(final int newCursorPosition) {
        resetComposingState(true /* alsoResetLastComposedWord */);
        if (mCurrentSettings.mBigramPredictionEnabled) {
            clearSuggestionStrip();
        } else {
            setSuggestionStrip(mCurrentSettings.mSuggestPuncList, false);
        }
        mConnection.finishComposingText();
        mConnection.resetCachesUponCursorMove(newCursorPosition);
    }

    private void resetComposingState(final boolean alsoResetLastComposedWord) {
@@ -1220,7 +1222,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        }
    }

    private void sendUpDownEnterOrBackspace(final int code) {
    private void sendDownUpKeyEventForBackwardCompatibility(final int code) {
        final long eventTime = SystemClock.uptimeMillis();
        mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
                KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
@@ -1234,7 +1236,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        // TODO: Remove this special handling of digit letters.
        // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
        if (code >= '0' && code <= '9') {
            super.sendKeyChar((char)code);
            sendDownUpKeyEventForBackwardCompatibility(code - '0' + KeyEvent.KEYCODE_0);
            if (ProductionFlag.IS_EXPERIMENTAL) {
                ResearchLogger.latinIME_sendKeyCodePoint(code);
            }
@@ -1249,7 +1251,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
            // 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.
            sendUpDownEnterOrBackspace(KeyEvent.KEYCODE_ENTER);
            sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_ENTER);
        } else {
            final String text = new String(new int[] { code }, 0, 1);
            mConnection.commitText(text, text.length());
@@ -1525,7 +1527,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                    // 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.
                    sendUpDownEnterOrBackspace(KeyEvent.KEYCODE_DEL);
                    sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_DEL);
                } else {
                    mConnection.deleteSurroundingText(1, 0);
                }
@@ -1862,7 +1864,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                    separatorString);
            if (!typedWord.equals(autoCorrection)) {
                // This will make the correction flash for a short while as a visual clue
                // to the user that auto-correction happened.
                // to the user that auto-correction happened. It has no other effect; in particular
                // note that this won't affect the text inside the text field AT ALL: it only makes
                // the segment of text starting at the supplied index and running for the length
                // of the auto-correction flash. At this moment, the "typedWord" argument is
                // ignored by TextView.
                mConnection.commitCorrection(
                        new CorrectionInfo(mLastSelectionEnd - typedWord.length(),
                        typedWord, autoCorrection));
@@ -2229,6 +2235,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        dialog.show();
    }

    public void debugDumpStateAndCrashWithException(final String context) {
        final StringBuilder s = new StringBuilder();
        s.append("Target application : ").append(mTargetApplicationInfo.name)
                .append("\nPackage : ").append(mTargetApplicationInfo.packageName)
                .append("\nTarget app sdk version : ")
                .append(mTargetApplicationInfo.targetSdkVersion)
                .append("\nAttributes : ").append(mCurrentSettings.getInputAttributesDebugString())
                .append("\nContext : ").append(context);
        throw new RuntimeException(s.toString());
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
        super.dump(fd, fout, args);
+178 −1
Original line number Diff line number Diff line
@@ -33,16 +33,47 @@ import com.android.inputmethod.research.ResearchLogger;
import java.util.regex.Pattern;

/**
 * Wrapper for InputConnection to simplify interaction
 * Enrichment class for InputConnection to simplify interaction and add functionality.
 *
 * This class serves as a wrapper to be able to simply add hooks to any calls to the underlying
 * InputConnection. It also keeps track of a number of things to avoid having to call upon IPC
 * all the time to find out what text is in the buffer, when we need it to determine caps mode
 * for example.
 */
public class RichInputConnection {
    private static final String TAG = RichInputConnection.class.getSimpleName();
    private static final boolean DBG = false;
    private static final boolean DEBUG_PREVIOUS_TEXT = false;
    // Provision for a long word pair and a separator
    private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1;
    private static final Pattern spaceRegex = Pattern.compile("\\s+");
    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.
     */
    private int mCurrentCursorPosition = 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.
     */
    private StringBuilder mCommittedTextBeforeComposingText = new StringBuilder();
    /**
     * This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
     */
    private StringBuilder mComposingText = new StringBuilder();
    /**
     * This is a one-character string containing the character after the cursor. Since LatinIME
     * never touches it directly, it's never modified by any means other than re-reading from the
     * TextView when the cursor position is changed by the user.
     */
    private CharSequence mCharAfterTheCursor = "";
    // 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;
    int mNestLevel;
@@ -52,6 +83,37 @@ public class RichInputConnection {
        mNestLevel = 0;
    }

    private void checkConsistencyForDebug() {
        final ExtractedTextRequest r = new ExtractedTextRequest();
        r.hintMaxChars = 0;
        r.hintMaxLines = 0;
        r.token = 1;
        r.flags = 0;
        final ExtractedText et = mIC.getExtractedText(r, 0);
        final CharSequence beforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
        final StringBuilder internal = new StringBuilder().append(mCommittedTextBeforeComposingText)
                .append(mComposingText);
        if (null == et || null == beforeCursor) return;
        final int actualLength = Math.min(beforeCursor.length(), internal.length());
        if (internal.length() > actualLength) {
            internal.delete(0, internal.length() - actualLength);
        }
        final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
                : beforeCursor.subSequence(beforeCursor.length() - actualLength,
                        beforeCursor.length()).toString();
        if (et.selectionStart != mCurrentCursorPosition
                || !(reference.equals(internal.toString()))) {
            final String context = "Expected cursor position = " + mCurrentCursorPosition
                    + "\nActual cursor position = " + et.selectionStart
                    + "\nExpected text = " + internal.length() + " " + internal
                    + "\nActual text = " + reference.length() + " " + reference;
            ((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
        } else {
            Log.e(TAG, Utils.getStackTrace(2));
            Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart);
        }
    }

    public void beginBatchEdit() {
        if (++mNestLevel == 1) {
            mIC = mParent.getCurrentInputConnection();
@@ -65,12 +127,30 @@ public class RichInputConnection {
                Log.e(TAG, "Nest level too deep : " + mNestLevel);
            }
        }
        checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
    }

    public void endBatchEdit() {
        if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
        if (--mNestLevel == 0 && null != mIC) {
            mIC.endBatchEdit();
        }
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
    }

    public void resetCachesUponCursorMove(final int newCursorPosition) {
        mCurrentCursorPosition = newCursorPosition;
        mComposingText.setLength(0);
        mCommittedTextBeforeComposingText.setLength(0);
        mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
        mCharAfterTheCursor = getTextAfterCursor(1, 0);
        if (null != mIC) {
            mIC.finishComposingText();
            if (ProductionFlag.IS_EXPERIMENTAL) {
                ResearchLogger.richInputConnection_finishComposingText();
            }
        }
    }

    private void checkBatchEdit() {
@@ -83,6 +163,10 @@ public class RichInputConnection {

    public void finishComposingText() {
        checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
        mCommittedTextBeforeComposingText.append(mComposingText);
        mCurrentCursorPosition += mComposingText.length();
        mComposingText.setLength(0);
        if (null != mIC) {
            mIC.finishComposingText();
            if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -93,6 +177,10 @@ public class RichInputConnection {

    public void commitText(final CharSequence text, final int i) {
        checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
        mCommittedTextBeforeComposingText.append(text);
        mCurrentCursorPosition += text.length() - mComposingText.length();
        mComposingText.setLength(0);
        if (null != mIC) {
            mIC.commitText(text, i);
            if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -121,12 +209,28 @@ public class RichInputConnection {

    public void deleteSurroundingText(final int i, final int j) {
        checkBatchEdit();
        final int remainingChars = mComposingText.length() - i;
        if (remainingChars >= 0) {
            mComposingText.setLength(remainingChars);
        } else {
            mComposingText.setLength(0);
            // Never cut under 0
            final int len = Math.max(mCommittedTextBeforeComposingText.length()
                    + remainingChars, 0);
            mCommittedTextBeforeComposingText.setLength(len);
        }
        if (mCurrentCursorPosition > i) {
            mCurrentCursorPosition -= i;
        } else {
            mCurrentCursorPosition = 0;
        }
        if (null != mIC) {
            mIC.deleteSurroundingText(i, j);
            if (ProductionFlag.IS_EXPERIMENTAL) {
                ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
            }
        }
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
    }

    public void performEditorAction(final int actionId) {
@@ -141,6 +245,44 @@ public class RichInputConnection {

    public void sendKeyEvent(final KeyEvent keyEvent) {
        checkBatchEdit();
        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
            if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
            // This method is only called for enter or backspace when speaking to old
            // applications (target SDK <= 15), or for digits.
            // When talking to new applications we never use this method because it's inherently
            // racy and has unpredictable results, but for backward compatibility we continue
            // sending the key events for only Enter and Backspace because some applications
            // mistakenly catch them to do some stuff.
            switch (keyEvent.getKeyCode()) {
                case KeyEvent.KEYCODE_ENTER:
                    mCommittedTextBeforeComposingText.append("\n");
                    mCurrentCursorPosition += 1;
                    break;
                case KeyEvent.KEYCODE_DEL:
                    if (0 == mComposingText.length()) {
                        if (mCommittedTextBeforeComposingText.length() > 0) {
                            mCommittedTextBeforeComposingText.delete(
                                    mCommittedTextBeforeComposingText.length() - 1,
                                    mCommittedTextBeforeComposingText.length());
                        }
                    } else {
                        mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
                    }
                    if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
                    break;
                case KeyEvent.KEYCODE_UNKNOWN:
                    if (null != keyEvent.getCharacters()) {
                        mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
                        mCurrentCursorPosition += keyEvent.getCharacters().length();
                    }
                    break;
                default:
                    final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
                    mCommittedTextBeforeComposingText.append(text);
                    mCurrentCursorPosition += text.length();
                    break;
            }
        }
        if (null != mIC) {
            mIC.sendKeyEvent(keyEvent);
            if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -151,48 +293,83 @@ public class RichInputConnection {

    public void setComposingText(final CharSequence text, final int i) {
        checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
        mCurrentCursorPosition += 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.
        if (null != mIC) {
            mIC.setComposingText(text, i);
            if (ProductionFlag.IS_EXPERIMENTAL) {
                ResearchLogger.richInputConnection_setComposingText(text, i);
            }
        }
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
    }

    public void setSelection(final int from, final int to) {
        checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
        if (null != mIC) {
            mIC.setSelection(from, to);
            if (ProductionFlag.IS_EXPERIMENTAL) {
                ResearchLogger.richInputConnection_setSelection(from, to);
            }
        }
        mCurrentCursorPosition = from;
        mCommittedTextBeforeComposingText.setLength(0);
        mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
    }

    public void commitCorrection(final CorrectionInfo correctionInfo) {
        checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
        // This has no effect on the text field and does not change its content. It only makes
        // TextView flash the text for a second based on indices contained in the argument.
        if (null != mIC) {
            mIC.commitCorrection(correctionInfo);
            if (ProductionFlag.IS_EXPERIMENTAL) {
                ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
            }
        }
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
    }

    public void commitCompletion(final CompletionInfo completionInfo) {
        checkBatchEdit();
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
        final CharSequence text = completionInfo.getText();
        mCommittedTextBeforeComposingText.append(text);
        mCurrentCursorPosition += text.length() - mComposingText.length();
        mComposingText.setLength(0);
        if (null != mIC) {
            mIC.commitCompletion(completionInfo);
            if (ProductionFlag.IS_EXPERIMENTAL) {
                ResearchLogger.richInputConnection_commitCompletion(completionInfo);
            }
        }
        if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
    }

    public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
        mIC = mParent.getCurrentInputConnection();
        if (null == mIC) return null;
        final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
        if (DEBUG_PREVIOUS_TEXT && null != prev) {
            final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
            final String reference = prev.length() <= checkLength ? prev.toString()
                    : prev.subSequence(prev.length() - checkLength, prev.length()).toString();
            final StringBuilder internal = new StringBuilder()
                    .append(mCommittedTextBeforeComposingText).append(mComposingText);
            if (internal.length() > checkLength) {
                internal.delete(0, internal.length() - checkLength);
                if (!(reference.equals(internal.toString()))) {
                    final String context =
                            "Expected text = " + internal + "\nActual text = " + reference;
                    ((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
                }
            }
        }
        return getNthPreviousWord(prev, sentenceSeperators, n);
    }