diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/RichInputConnection.java | 213 |
1 files changed, 76 insertions, 137 deletions
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 37311acf2..673d1b4c2 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -57,19 +57,14 @@ public final class RichInputConnection { private static final int INVALID_CURSOR_POSITION = -1; /** - * This variable contains an expected value for the selection start position. This is where the - * cursor or selection start may end up after all the keyboard-triggered updates have passed. We - * keep this to compare it to the actual selection start to guess whether the move was caused by - * a keyboard command or not. - * It's not really the selection start position: the selection start may not be there yet, and - * in some cases, it may never arrive there. + * This variable contains an expected value for the cursor position. This is where the + * cursor may end up after all the keyboard-triggered updates have passed. We keep this to + * compare it to the actual cursor position to guess whether the move was caused by a + * keyboard command or not. + * It's not really the cursor position: the cursor may not be there yet, and it's also expected + * there be cases where it never actually comes to be there. */ - private int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points - /** - * The expected selection end. Only differs from mExpectedSelStart if a non-empty selection is - * expected. The same caveats as mExpectedSelStart apply. - */ - private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points + private int mExpectedCursorPosition = 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. @@ -108,16 +103,16 @@ public final class RichInputConnection { final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString() : beforeCursor.subSequence(beforeCursor.length() - actualLength, beforeCursor.length()).toString(); - if (et.selectionStart != mExpectedSelStart + if (et.selectionStart != mExpectedCursorPosition || !(reference.equals(internal.toString()))) { - final String context = "Expected selection start = " + mExpectedSelStart - + "\nActual selection start = " + et.selectionStart + final String context = "Expected cursor position = " + mExpectedCursorPosition + + "\nActual cursor position = " + et.selectionStart + "\nExpected text = " + internal.length() + " " + internal + "\nActual text = " + reference.length() + " " + reference; ((LatinIME)mParent).debugDumpStateAndCrashWithException(context); } else { Log.e(TAG, DebugLogUtils.getStackTrace(2)); - Log.e(TAG, "Exp <> Actual : " + mExpectedSelStart + " <> " + et.selectionStart); + Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart); } } @@ -155,50 +150,16 @@ public final class RichInputConnection { * 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 newSelStart the new position of the selection start, as received from the system. - * @param newSelEnd the new position of the selection end, as received from the system. - * @param shouldFinishComposition whether we should finish the composition in progress. + * @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 newSelStart, - final int newSelEnd, final boolean shouldFinishComposition) { - mExpectedSelStart = newSelStart; - mExpectedSelEnd = newSelEnd; + public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newCursorPosition, + final boolean shouldFinishComposition) { + mExpectedCursorPosition = newCursorPosition; mComposingText.setLength(0); - final boolean didReloadTextSuccessfully = reloadTextCache(); - if (!didReloadTextSuccessfully) { - Log.d(TAG, "Will try to retrieve text later."); - return false; - } - final int lengthOfTextBeforeCursor = mCommittedTextBeforeComposingText.length(); - if (lengthOfTextBeforeCursor > newSelStart - || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE - && newSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) { - // newSelStart and newSelEnd 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 newSelStart says, then we know it was lying. In both - // cases the length is more reliable. Note that we only have to check newSelStart (not - // newSelEnd) since if newSelEnd is wrong, the newSelStart will be wrong as well. - mExpectedSelStart = lengthOfTextBeforeCursor; - mExpectedSelEnd = lengthOfTextBeforeCursor; - } - if (null != mIC && shouldFinishComposition) { - mIC.finishComposingText(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_finishComposingText(); - } - } - return true; - } - - /** - * Reload the cached text from the InputConnection. - * - * @return true if successful - */ - private boolean reloadTextCache() { mCommittedTextBeforeComposingText.setLength(0); mIC = mParent.getCurrentInputConnection(); // Call upon the inputconnection directly since our own method is using the cache, and @@ -208,12 +169,27 @@ public final class RichInputConnection { 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. - mExpectedSelStart = INVALID_CURSOR_POSITION; - mExpectedSelEnd = INVALID_CURSOR_POSITION; - Log.e(TAG, "Unable to connect to the editor to retrieve text."); + 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; } @@ -242,8 +218,7 @@ public final class RichInputConnection { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); mCommittedTextBeforeComposingText.append(text); - mExpectedSelStart += text.length() - mComposingText.length(); - mExpectedSelEnd = mExpectedSelStart; + mExpectedCursorPosition += text.length() - mComposingText.length(); mComposingText.setLength(0); if (null != mIC) { mIC.commitText(text, i); @@ -256,7 +231,7 @@ public final class RichInputConnection { } public boolean canDeleteCharacters() { - return mExpectedSelStart > 0; + return mExpectedCursorPosition > 0; } /** @@ -293,10 +268,11 @@ public final class RichInputConnection { // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so. // 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 != mExpectedSelStart) { - if (!reloadTextCache()) { - Log.w(TAG, "Unable to connect to the editor. " - + "Setting caps mode without knowing text."); + if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) { + final CharSequence textBeforeCursor = getTextBeforeCursor( + Constants.EDITOR_CONTENTS_CACHE_SIZE, 0); + if (!TextUtils.isEmpty(textBeforeCursor)) { + mCommittedTextBeforeComposingText.append(textBeforeCursor); } } // This never calls InputConnection#getCapsMode - in fact, it's a static method that @@ -319,8 +295,8 @@ public final class RichInputConnection { // However, if we don't have an expected cursor position, then we should always // go fetch the cache again (as it happens, INVALID_CURSOR_POSITION < 0, so we need to // test for this explicitly) - if (INVALID_CURSOR_POSITION != mExpectedSelStart - && (cachedLength >= n || cachedLength >= mExpectedSelStart)) { + if (INVALID_CURSOR_POSITION != mExpectedCursorPosition + && (cachedLength >= n || cachedLength >= mExpectedCursorPosition)) { final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText); // We call #toString() here to create a temporary object. // In some situations, this method is called on a worker thread, and it's possible @@ -360,14 +336,10 @@ public final class RichInputConnection { + remainingChars, 0); mCommittedTextBeforeComposingText.setLength(len); } - if (mExpectedSelStart > beforeLength) { - mExpectedSelStart -= beforeLength; - mExpectedSelEnd -= beforeLength; + if (mExpectedCursorPosition > beforeLength) { + mExpectedCursorPosition -= beforeLength; } else { - // There are fewer characters before the cursor in the buffer than we are being asked to - // delete. Only delete what is there. - mExpectedSelStart = 0; - mExpectedSelEnd -= mExpectedSelStart; + mExpectedCursorPosition = 0; } if (null != mIC) { mIC.deleteSurroundingText(beforeLength, afterLength); @@ -401,8 +373,7 @@ public final class RichInputConnection { switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_ENTER: mCommittedTextBeforeComposingText.append("\n"); - mExpectedSelStart += 1; - mExpectedSelEnd = mExpectedSelStart; + mExpectedCursorPosition += 1; break; case KeyEvent.KEYCODE_DEL: if (0 == mComposingText.length()) { @@ -414,24 +385,18 @@ public final class RichInputConnection { } else { mComposingText.delete(mComposingText.length() - 1, mComposingText.length()); } - if (mExpectedSelStart > 0 && mExpectedSelStart == mExpectedSelEnd) { - // TODO: Handle surrogate pairs. - mExpectedSelStart -= 1; - } - mExpectedSelEnd = mExpectedSelStart; + if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1; break; case KeyEvent.KEYCODE_UNKNOWN: if (null != keyEvent.getCharacters()) { mCommittedTextBeforeComposingText.append(keyEvent.getCharacters()); - mExpectedSelStart += keyEvent.getCharacters().length(); - mExpectedSelEnd = mExpectedSelStart; + mExpectedCursorPosition += keyEvent.getCharacters().length(); } break; default: - final String text = StringUtils.newSingleCodePointString(keyEvent.getUnicodeChar()); + final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1); mCommittedTextBeforeComposingText.append(text); - mExpectedSelStart += text.length(); - mExpectedSelEnd = mExpectedSelStart; + mExpectedCursorPosition += text.length(); break; } } @@ -465,12 +430,10 @@ public final class RichInputConnection { public void setComposingText(final CharSequence text, final int newCursorPosition) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - mExpectedSelStart += text.length() - mComposingText.length(); - mExpectedSelEnd = mExpectedSelStart; + mExpectedCursorPosition += text.length() - mComposingText.length(); mComposingText.setLength(0); mComposingText.append(text); - // TODO: support values of newCursorPosition != 1. At this time, this is never called with - // newCursorPosition != 1. + // TODO: support values of i != 1. At this time, this is never called with i != 1. if (null != mIC) { mIC.setComposingText(text, newCursorPosition); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -480,31 +443,19 @@ public final class RichInputConnection { if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } - /** - * Set the selection of the text editor. - * - * Calls through to {@link InputConnection#setSelection(int, int)}. - * - * @param start the character index where the selection should start. - * @param end the character index where the selection should end. - * @return Returns true on success, false if the input connection is no longer valid either when - * setting the selection or when retrieving the text cache at that point. - */ - public boolean setSelection(final int start, final int end) { + public void setSelection(final int start, final int end) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - mExpectedSelStart = start; - mExpectedSelEnd = end; if (null != mIC) { - final boolean isIcValid = mIC.setSelection(start, end); - if (!isIcValid) { - return false; - } + mIC.setSelection(start, end); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.richInputConnection_setSelection(start, end); } } - return reloadTextCache(); + mExpectedCursorPosition = start; + mCommittedTextBeforeComposingText.setLength(0); + mCommittedTextBeforeComposingText.append( + getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0)); } public void commitCorrection(final CorrectionInfo correctionInfo) { @@ -525,8 +476,7 @@ public final class RichInputConnection { // text should never be null, but just in case, it's better to insert nothing than to crash if (null == text) text = ""; mCommittedTextBeforeComposingText.append(text); - mExpectedSelStart += text.length() - mComposingText.length(); - mExpectedSelEnd = mExpectedSelStart; + mExpectedCursorPosition += text.length() - mComposingText.length(); mComposingText.setLength(0); if (null != mIC) { mIC.commitCompletion(completionInfo); @@ -538,7 +488,7 @@ public final class RichInputConnection { } @SuppressWarnings("unused") - public String getNthPreviousWord(final SettingsValues currentSettingsValues, final int n) { + public String getNthPreviousWord(final String sentenceSeperators, final int n) { mIC = mParent.getCurrentInputConnection(); if (null == mIC) return null; final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); @@ -557,7 +507,7 @@ public final class RichInputConnection { } } } - return getNthPreviousWord(prev, currentSettingsValues, n); + return getNthPreviousWord(prev, sentenceSeperators, n); } private static boolean isSeparator(int code, String sep) { @@ -581,7 +531,7 @@ public final class RichInputConnection { // (n = 2) "abc |" -> null // (n = 2) "abc. def|" -> null public static String getNthPreviousWord(final CharSequence prev, - final SettingsValues currentSettingsValues, final int n) { + final String sentenceSeperators, final int n) { if (prev == null) return null; final String[] w = spaceRegex.split(prev); @@ -593,8 +543,7 @@ public final class RichInputConnection { // If ends in a separator, return null final char lastChar = nthPrevWord.charAt(length - 1); - if (currentSettingsValues.isWordSeparator(lastChar) - || currentSettingsValues.isWordConnector(lastChar)) return null; + if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; return nthPrevWord; } @@ -809,30 +758,20 @@ public final class RichInputConnection { * this update and not the ones in-between. This is almost impossible to achieve even trying * very very hard. * - * @param oldSelStart The value of the old selection in the update. - * @param newSelStart The value of the new selection in the update. - * @param oldSelEnd The value of the old selection end in the update. - * @param newSelEnd The value of the new selection end in the update. + * @param oldSelStart The value of the old cursor position in the update. + * @param newSelStart The value of the new cursor position in the update. * @return whether this is a belated expected update or not. */ - public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart, - final int oldSelEnd, final int newSelEnd) { - // This update is "belated" if we are expecting it. That is, mExpectedSelStart and - // mExpectedSelEnd match the new values that the TextView is updating TO. - if (mExpectedSelStart == newSelStart && mExpectedSelEnd == newSelEnd) return true; - // This update is not belated if mExpectedSelStart and mExpeectedSelend match the old - // values, and one of newSelStart or newSelEnd is updated to a different value. In this - // case, there is likely something other than the IME that has moved the selection endpoint - // to the new value. - if (mExpectedSelStart == oldSelStart && mExpectedSelEnd == oldSelEnd - && (oldSelStart != newSelStart || oldSelEnd != newSelEnd)) return false; - // If nether of the above two cases holds, then the system may be having trouble keeping up - // with updates. If 1) the selection is a cursor, 2) newSelStart is between oldSelStart - // and mExpectedSelStart, and 3) newSelEnd is between oldSelEnd and mExpectedSelEnd, then - // assume a belated update. - return (newSelStart == newSelEnd) - && (newSelStart - oldSelStart) * (mExpectedSelStart - newSelStart) >= 0 - && (newSelEnd - oldSelEnd) * (mExpectedSelEnd - newSelEnd) >= 0; + public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) { + // If this is an update that arrives at our expected position, it's a belated update. + if (newSelStart == mExpectedCursorPosition) return true; + // If this is an update that moves the cursor from our expected position, it must be + // an explicit move. + if (oldSelStart == mExpectedCursorPosition) return false; + // The following returns true if newSelStart is between oldSelStart and + // mCurrentCursorPosition. We assume that if the updated position is between the old + // position and the expected position, then it must be a belated update. + return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0; } /** |