aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/RichInputConnection.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java')
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java166
1 files changed, 81 insertions, 85 deletions
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 980215de6..b69e3f8d2 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import android.inputmethodservice.InputMethodService;
-import android.text.SpannableString;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -28,6 +27,11 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.utils.CapsModeUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.ResearchLogger;
import java.util.Locale;
@@ -47,16 +51,19 @@ public final class RichInputConnection {
private static final boolean DEBUG_PREVIOUS_TEXT = false;
private static final boolean DEBUG_BATCH_NESTING = false;
// Provision for a long word pair and a separator
- private static final int LOOKBACK_CHARACTER_NUM = Constants.Dictionary.MAX_WORD_LENGTH * 2 + 1;
+ private static final int LOOKBACK_CHARACTER_NUM = Constants.DICTIONARY_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.
+ * 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 mCurrentCursorPosition = 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.
@@ -97,16 +104,16 @@ public final class RichInputConnection {
final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
: beforeCursor.subSequence(beforeCursor.length() - actualLength,
beforeCursor.length()).toString();
- if (et.selectionStart != mCurrentCursorPosition
+ if (et.selectionStart != mExpectedCursorPosition
|| !(reference.equals(internal.toString()))) {
- final String context = "Expected cursor position = " + mCurrentCursorPosition
+ 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, Utils.getStackTrace(2));
- Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart);
+ Log.e(TAG, DebugLogUtils.getStackTrace(2));
+ Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
}
}
@@ -137,7 +144,7 @@ public final class RichInputConnection {
public void resetCachesUponCursorMove(final int newCursorPosition,
final boolean shouldFinishComposition) {
- mCurrentCursorPosition = newCursorPosition;
+ mExpectedCursorPosition = newCursorPosition;
mComposingText.setLength(0);
mCommittedTextBeforeComposingText.setLength(0);
final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
@@ -154,7 +161,7 @@ public final class RichInputConnection {
if (mNestLevel != 1) {
// TODO: exception instead
Log.e(TAG, "Batch edit level incorrect : " + mNestLevel);
- Log.e(TAG, Utils.getStackTrace(4));
+ Log.e(TAG, DebugLogUtils.getStackTrace(4));
}
}
@@ -162,7 +169,7 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(mComposingText);
- mCurrentCursorPosition += mComposingText.length();
+ mExpectedCursorPosition += mComposingText.length();
mComposingText.setLength(0);
if (null != mIC) {
mIC.finishComposingText();
@@ -176,7 +183,7 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(text);
- mCurrentCursorPosition += text.length() - mComposingText.length();
+ mExpectedCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitText(text, i);
@@ -188,6 +195,10 @@ public final class RichInputConnection {
return mIC.getSelectedText(flags);
}
+ public boolean canDeleteCharacters() {
+ return mExpectedCursorPosition > 0;
+ }
+
/**
* Gets the caps modes we should be in after this specific string.
*
@@ -222,7 +233,7 @@ 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 != mCurrentCursorPosition) {
+ if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
mCommittedTextBeforeComposingText.append(
getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
}
@@ -238,22 +249,35 @@ public final class RichInputConnection {
mCommittedTextBeforeComposingText.length());
}
- public CharSequence getTextBeforeCursor(final int i, final int j) {
- // TODO: use mCommittedTextBeforeComposingText if possible to improve performance
+ public CharSequence getTextBeforeCursor(final int n, final int flags) {
+ final int cachedLength =
+ mCommittedTextBeforeComposingText.length() + mComposingText.length();
+ // If we have enough characters to satisfy the request, or if we have all characters in
+ // the text field, then we can return the cached version right away.
+ if (cachedLength >= n || cachedLength >= mExpectedCursorPosition) {
+ final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
+ s.append(mComposingText);
+ if (s.length() > n) {
+ s.delete(0, s.length() - n);
+ }
+ return s;
+ }
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) return mIC.getTextBeforeCursor(i, j);
+ if (null != mIC) {
+ return mIC.getTextBeforeCursor(n, flags);
+ }
return null;
}
- public CharSequence getTextAfterCursor(final int i, final int j) {
+ public CharSequence getTextAfterCursor(final int n, final int flags) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) return mIC.getTextAfterCursor(i, j);
+ if (null != mIC) return mIC.getTextAfterCursor(n, flags);
return null;
}
- public void deleteSurroundingText(final int i, final int j) {
+ public void deleteSurroundingText(final int beforeLength, final int afterLength) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
- final int remainingChars = mComposingText.length() - i;
+ final int remainingChars = mComposingText.length() - beforeLength;
if (remainingChars >= 0) {
mComposingText.setLength(remainingChars);
} else {
@@ -263,15 +287,15 @@ public final class RichInputConnection {
+ remainingChars, 0);
mCommittedTextBeforeComposingText.setLength(len);
}
- if (mCurrentCursorPosition > i) {
- mCurrentCursorPosition -= i;
+ if (mExpectedCursorPosition > beforeLength) {
+ mExpectedCursorPosition -= beforeLength;
} else {
- mCurrentCursorPosition = 0;
+ mExpectedCursorPosition = 0;
}
if (null != mIC) {
- mIC.deleteSurroundingText(i, j);
+ mIC.deleteSurroundingText(beforeLength, afterLength);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+ ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength);
}
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -300,7 +324,7 @@ public final class RichInputConnection {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER:
mCommittedTextBeforeComposingText.append("\n");
- mCurrentCursorPosition += 1;
+ mExpectedCursorPosition += 1;
break;
case KeyEvent.KEYCODE_DEL:
if (0 == mComposingText.length()) {
@@ -312,18 +336,18 @@ public final class RichInputConnection {
} else {
mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
}
- if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
+ if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
break;
case KeyEvent.KEYCODE_UNKNOWN:
if (null != keyEvent.getCharacters()) {
mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
- mCurrentCursorPosition += keyEvent.getCharacters().length();
+ mExpectedCursorPosition += keyEvent.getCharacters().length();
}
break;
default:
final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
mCommittedTextBeforeComposingText.append(text);
- mCurrentCursorPosition += text.length();
+ mExpectedCursorPosition += text.length();
break;
}
}
@@ -338,7 +362,6 @@ public final class RichInputConnection {
public void setComposingRegion(final int start, final int end) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- mCurrentCursorPosition = end;
final CharSequence textBeforeCursor =
getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
mCommittedTextBeforeComposingText.setLength(0);
@@ -355,32 +378,32 @@ public final class RichInputConnection {
}
}
- public void setComposingText(final CharSequence text, final int i) {
+ public void setComposingText(final CharSequence text, final int newCursorPosition) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- mCurrentCursorPosition += text.length() - mComposingText.length();
+ mExpectedCursorPosition += 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);
+ mIC.setComposingText(text, newCursorPosition);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setComposingText(text, i);
+ ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition);
}
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
- public void setSelection(final int from, final int to) {
+ public void setSelection(final int start, final int end) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
if (null != mIC) {
- mIC.setSelection(from, to);
+ mIC.setSelection(start, end);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setSelection(from, to);
+ ResearchLogger.richInputConnection_setSelection(start, end);
}
}
- mCurrentCursorPosition = from;
+ mExpectedCursorPosition = start;
mCommittedTextBeforeComposingText.setLength(0);
mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
}
@@ -403,7 +426,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);
- mCurrentCursorPosition += text.length() - mComposingText.length();
+ mExpectedCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
@@ -418,7 +441,7 @@ public final class RichInputConnection {
public String getNthPreviousWord(final String sentenceSeperators, final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) return null;
- final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+ final CharSequence prev = 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()
@@ -437,32 +460,6 @@ public final class RichInputConnection {
return getNthPreviousWord(prev, sentenceSeperators, n);
}
- /**
- * Represents a range of text, relative to the current cursor position.
- */
- public static final class Range {
- /** Characters before selection start */
- public final int mCharsBefore;
-
- /**
- * Characters after selection start, including one trailing word
- * separator.
- */
- public final int mCharsAfter;
-
- /** The actual characters that make up a word */
- public final CharSequence mWord;
-
- public Range(int charsBefore, int charsAfter, CharSequence word) {
- if (charsBefore < 0 || charsAfter < 0) {
- throw new IndexOutOfBoundsException();
- }
- this.mCharsBefore = charsBefore;
- this.mCharsAfter = charsAfter;
- this.mWord = word;
- }
- }
-
private static boolean isSeparator(int code, String sep) {
return sep.indexOf(code) != -1;
}
@@ -509,7 +506,7 @@ public final class RichInputConnection {
*/
public CharSequence getWordAtCursor(String separators) {
// getWordRangeAtCursor returns null if the connection is null
- Range r = getWordRangeAtCursor(separators, 0);
+ TextRange r = getWordRangeAtCursor(separators, 0);
return (r == null) ? null : r.mWord;
}
@@ -521,7 +518,8 @@ public final class RichInputConnection {
* be included in the returned range
* @return a range containing the text surrounding the cursor
*/
- public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) {
+ public TextRange getWordRangeAtCursor(final String sep,
+ final int additionalPrecedingWordsCount) {
mIC = mParent.getCurrentInputConnection();
if (mIC == null || sep == null) {
return null;
@@ -571,19 +569,18 @@ public final class RichInputConnection {
}
}
- final SpannableString word = new SpannableString(TextUtils.concat(
- before.subSequence(startIndexInBefore, before.length()),
- after.subSequence(0, endIndexInAfter)));
- return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
+ return new TextRange(TextUtils.concat(before, after), startIndexInBefore,
+ before.length() + endIndexInAfter, before.length());
}
public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
- final CharSequence before = getTextBeforeCursor(1, 0);
- final CharSequence after = getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0))
- && !settingsValues.isWordConnector(before.charAt(0))) {
+ final int codePointBeforeCursor = getCodePointBeforeCursor();
+ if (Constants.NOT_A_CODE != codePointBeforeCursor
+ && !settingsValues.isWordSeparator(codePointBeforeCursor)
+ && !settingsValues.isWordConnector(codePointBeforeCursor)) {
return true;
}
+ final CharSequence after = getTextAfterCursor(1, 0);
if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0))
&& !settingsValues.isWordConnector(after.charAt(0))) {
return true;
@@ -593,9 +590,8 @@ public final class RichInputConnection {
public void removeTrailingSpace() {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
- final CharSequence lastOne = getTextBeforeCursor(1, 0);
- if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == Constants.CODE_SPACE) {
+ final int codePointBeforeCursor = getCodePointBeforeCursor();
+ if (Constants.CODE_SPACE == codePointBeforeCursor) {
deleteSurroundingText(1, 0);
}
}
@@ -650,7 +646,7 @@ public final class RichInputConnection {
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
final String periodSpace = ". ";
- if (!periodSpace.equals(textBeforeCursor)) {
+ if (!TextUtils.equals(periodSpace, textBeforeCursor)) {
// Theoretically we should not be coming here if there isn't ". " before the
// cursor, but the application may be changing the text while we are typing, so
// anything goes. We should not crash.
@@ -712,14 +708,14 @@ public final class RichInputConnection {
*/
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 == mCurrentCursorPosition) return true;
+ 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 == mCurrentCursorPosition) return false;
+ 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) * (mCurrentCursorPosition - newSelStart) >= 0;
+ return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
}
/**