Merge "Detect cases where rotation messes with initialization"
commit
8f23c6f78b
|
@ -138,6 +138,9 @@ public final class Constants {
|
||||||
public static final int SPELL_CHECKER_COORDINATE = -3;
|
public static final int SPELL_CHECKER_COORDINATE = -3;
|
||||||
public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;
|
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
|
// Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
|
||||||
public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
|
public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
|
||||||
|
|
|
@ -233,6 +233,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
private static final int MSG_RESUME_SUGGESTIONS = 4;
|
private static final int MSG_RESUME_SUGGESTIONS = 4;
|
||||||
private static final int MSG_REOPEN_DICTIONARIES = 5;
|
private static final int MSG_REOPEN_DICTIONARIES = 5;
|
||||||
private static final int MSG_ON_END_BATCH_INPUT = 6;
|
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_NOT_GESTURE_INPUT = 0;
|
||||||
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
|
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:
|
case MSG_ON_END_BATCH_INPUT:
|
||||||
latinIme.onEndBatchInputAsyncInternal((SuggestedWords) msg.obj);
|
latinIme.onEndBatchInputAsyncInternal((SuggestedWords) msg.obj);
|
||||||
break;
|
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);
|
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() {
|
public void cancelUpdateSuggestionStrip() {
|
||||||
removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
|
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.
|
// span, so we should reset our state unconditionally, even if restarting is true.
|
||||||
mEnteredText = null;
|
mEnteredText = null;
|
||||||
resetComposingState(true /* alsoResetLastComposedWord */);
|
resetComposingState(true /* alsoResetLastComposedWord */);
|
||||||
if (isDifferentTextField) mHandler.postResumeSuggestions();
|
|
||||||
mDeleteCount = 0;
|
mDeleteCount = 0;
|
||||||
mSpaceState = SPACE_STATE_NONE;
|
mSpaceState = SPACE_STATE_NONE;
|
||||||
mRecapitalizeStatus.deactivate();
|
mRecapitalizeStatus.deactivate();
|
||||||
|
@ -871,8 +881,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
mSuggestedWords = SuggestedWords.EMPTY;
|
mSuggestedWords = SuggestedWords.EMPTY;
|
||||||
|
|
||||||
mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart,
|
// Sometimes, while rotating, for some reason the framework tells the app we are not
|
||||||
false /* shouldFinishComposition */);
|
// 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) {
|
if (isDifferentTextField) {
|
||||||
mainKeyboardView.closing();
|
mainKeyboardView.closing();
|
||||||
|
@ -899,6 +917,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
mLastSelectionStart = editorInfo.initialSelStart;
|
mLastSelectionStart = editorInfo.initialSelStart;
|
||||||
mLastSelectionEnd = editorInfo.initialSelEnd;
|
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.cancelUpdateSuggestionStrip();
|
||||||
mHandler.cancelDoubleSpacePeriodTimer();
|
mHandler.cancelDoubleSpacePeriodTimer();
|
||||||
|
@ -918,6 +939,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
|
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
|
// Initialization of personalization debug settings. This must be called inside
|
||||||
// onStartInputView.
|
// onStartInputView.
|
||||||
private void initPersonalizationDebugSettings(SettingsValues currentSettingsValues) {
|
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,
|
// 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
|
// we still want to tell the rich input connection about the new cursor position so
|
||||||
// that it can update its caches.
|
// that it can update its caches.
|
||||||
mConnection.resetCachesUponCursorMove(newSelStart,
|
mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart,
|
||||||
false /* shouldFinishComposition */);
|
false /* shouldFinishComposition */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1308,7 +1358,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
} else {
|
} else {
|
||||||
setSuggestedWords(settingsValues.mSuggestPuncList, false);
|
setSuggestedWords(settingsValues.mSuggestPuncList, false);
|
||||||
}
|
}
|
||||||
mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
|
mConnection.resetCachesUponCursorMoveAndReturnSuccess(newCursorPosition,
|
||||||
|
shouldFinishComposition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetComposingState(final boolean alsoResetLastComposedWord) {
|
private void resetComposingState(final boolean alsoResetLastComposedWord) {
|
||||||
|
@ -2853,6 +2904,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mHandler.postUpdateSuggestionStrip();
|
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() {
|
private void revertCommit() {
|
||||||
final String previousWord = mLastComposedWord.mPrevWord;
|
final String previousWord = mLastComposedWord.mPrevWord;
|
||||||
final String originallyTypedWord = mLastComposedWord.mTypedWord;
|
final String originallyTypedWord = mLastComposedWord.mTypedWord;
|
||||||
|
|
|
@ -73,9 +73,6 @@ public final class RichInputConnection {
|
||||||
* This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
|
* This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
|
||||||
*/
|
*/
|
||||||
private final StringBuilder mComposingText = new StringBuilder();
|
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;
|
private final InputMethodService mParent;
|
||||||
InputConnection mIC;
|
InputConnection mIC;
|
||||||
|
@ -93,7 +90,8 @@ public final class RichInputConnection {
|
||||||
r.token = 1;
|
r.token = 1;
|
||||||
r.flags = 0;
|
r.flags = 0;
|
||||||
final ExtractedText et = mIC.getExtractedText(r, 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)
|
final StringBuilder internal = new StringBuilder().append(mCommittedTextBeforeComposingText)
|
||||||
.append(mComposingText);
|
.append(mComposingText);
|
||||||
if (null == et || null == beforeCursor) return;
|
if (null == et || null == beforeCursor) return;
|
||||||
|
@ -142,19 +140,56 @@ public final class RichInputConnection {
|
||||||
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
|
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) {
|
final boolean shouldFinishComposition) {
|
||||||
mExpectedCursorPosition = newCursorPosition;
|
mExpectedCursorPosition = newCursorPosition;
|
||||||
mComposingText.setLength(0);
|
mComposingText.setLength(0);
|
||||||
mCommittedTextBeforeComposingText.setLength(0);
|
mCommittedTextBeforeComposingText.setLength(0);
|
||||||
final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
|
mIC = mParent.getCurrentInputConnection();
|
||||||
if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor);
|
// 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) {
|
if (null != mIC && shouldFinishComposition) {
|
||||||
mIC.finishComposingText();
|
mIC.finishComposingText();
|
||||||
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
|
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
|
||||||
ResearchLogger.richInputConnection_finishComposingText();
|
ResearchLogger.richInputConnection_finishComposingText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkBatchEdit() {
|
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
|
// getCapsMode should be updated to be able to return a "not enough info" result so that
|
||||||
// we can get more context only when needed.
|
// we can get more context only when needed.
|
||||||
if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
|
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)) {
|
if (!TextUtils.isEmpty(textBeforeCursor)) {
|
||||||
mCommittedTextBeforeComposingText.append(textBeforeCursor);
|
mCommittedTextBeforeComposingText.append(textBeforeCursor);
|
||||||
}
|
}
|
||||||
|
@ -364,7 +400,7 @@ public final class RichInputConnection {
|
||||||
if (DEBUG_BATCH_NESTING) checkBatchEdit();
|
if (DEBUG_BATCH_NESTING) checkBatchEdit();
|
||||||
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
|
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
|
||||||
final CharSequence textBeforeCursor =
|
final CharSequence textBeforeCursor =
|
||||||
getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
|
getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE + (end - start), 0);
|
||||||
mCommittedTextBeforeComposingText.setLength(0);
|
mCommittedTextBeforeComposingText.setLength(0);
|
||||||
if (!TextUtils.isEmpty(textBeforeCursor)) {
|
if (!TextUtils.isEmpty(textBeforeCursor)) {
|
||||||
final int indexOfStartOfComposingText =
|
final int indexOfStartOfComposingText =
|
||||||
|
@ -406,7 +442,8 @@ public final class RichInputConnection {
|
||||||
}
|
}
|
||||||
mExpectedCursorPosition = start;
|
mExpectedCursorPosition = start;
|
||||||
mCommittedTextBeforeComposingText.setLength(0);
|
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) {
|
public void commitCorrection(final CorrectionInfo correctionInfo) {
|
||||||
|
@ -525,9 +562,9 @@ public final class RichInputConnection {
|
||||||
if (mIC == null || sep == null) {
|
if (mIC == null || sep == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final CharSequence before = mIC.getTextBeforeCursor(1000,
|
final CharSequence before = mIC.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE,
|
||||||
InputConnection.GET_TEXT_WITH_STYLES);
|
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);
|
InputConnection.GET_TEXT_WITH_STYLES);
|
||||||
if (before == null || after == null) {
|
if (before == null || after == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
Loading…
Reference in New Issue