Loading java/src/com/android/inputmethod/latin/LatinIME.java +25 −8 Original line number Diff line number Diff line Loading @@ -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"); } Loading Loading @@ -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(); Loading Loading @@ -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) { Loading Loading @@ -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, Loading @@ -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); } Loading @@ -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()); Loading Loading @@ -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); } Loading Loading @@ -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)); Loading Loading @@ -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); Loading java/src/com/android/inputmethod/latin/RichInputConnection.java +178 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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() { Loading @@ -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) { Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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); } Loading Loading
java/src/com/android/inputmethod/latin/LatinIME.java +25 −8 Original line number Diff line number Diff line Loading @@ -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"); } Loading Loading @@ -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(); Loading Loading @@ -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) { Loading Loading @@ -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, Loading @@ -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); } Loading @@ -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()); Loading Loading @@ -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); } Loading Loading @@ -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)); Loading Loading @@ -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); Loading
java/src/com/android/inputmethod/latin/RichInputConnection.java +178 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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() { Loading @@ -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) { Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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); } Loading