Skip to content

Commit

Permalink
Index based cursor move and delete swipe fallback for indexless inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
rkkr committed May 24, 2020
1 parent 5ca7256 commit 5c1e495
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 81 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android {
minSdkVersion 19
targetSdkVersion 29
versionCode 69
versionName "3.19"
versionName "3.20"
}
buildTypes {
release {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,11 +522,6 @@ void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restart
// mLastSelection{Start,End} are reset later in this method, no need to do it here
needToCallLoadKeyboardLater = true;
} else {
// When rotating, and when input is starting again in a field from where the focus
// didn't move (the keyboard having been closed with the back key),
// initialSelStart and initialSelEnd sometimes are lying. Make a best effort to
// work around this bug.
mInputLogic.mConnection.tryFixLyingCursorPosition();
needToCallLoadKeyboardLater = false;
}
} else {
Expand Down Expand Up @@ -758,19 +753,30 @@ public boolean onCustomRequest(final int requestCode) {

@Override
public void onMovePointer(int steps) {
for (;steps < 0; steps++)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT);
for (;steps > 0; steps--)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT);
if (mInputLogic.mConnection.hasCursorPosition()) {
final int end = mInputLogic.mConnection.getExpectedSelectionEnd() + steps;
final int start = mInputLogic.mConnection.hasSelection() ? mInputLogic.mConnection.getExpectedSelectionStart() : end;
mInputLogic.mConnection.setSelection(start, end);
} else {
for (; steps < 0; steps++)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT);
for (; steps > 0; steps--)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT);
}
}

@Override
public void onMoveDeletePointer(int steps) {
int end = mInputLogic.mConnection.getExpectedSelectionEnd();
int start = mInputLogic.mConnection.getExpectedSelectionStart() + steps;
if (start > end)
return;
mInputLogic.mConnection.setSelection(start, end);
if (mInputLogic.mConnection.hasCursorPosition()) {
final int end = mInputLogic.mConnection.getExpectedSelectionEnd();
final int start = mInputLogic.mConnection.getExpectedSelectionStart() + steps;
if (start > end)
return;
mInputLogic.mConnection.setSelection(start, end);
} else {
for (; steps < 0; steps++)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ public final class RichInputConnection {
*/
private static final long SLOW_INPUT_CONNECTION_ON_FULL_RELOAD_MS = 1000;
/**
* The amount of time a {@link #getTextBeforeCursor} or {@link #getTextAfterCursor} call needs
* to take for the keyboard to enter the {@link #hasSlowInputConnection} state.
* The amount of time a {@link #getTextBeforeCursor} call needs
*/
private static final long SLOW_INPUT_CONNECTION_ON_PARTIAL_RELOAD_MS = 200;

Expand All @@ -75,12 +74,12 @@ public final class RichInputConnection {
* 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.
*/
public int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points
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.
*/
public int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points
private int mExpectedSelEnd = 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.
Expand Down Expand Up @@ -256,8 +255,10 @@ public void commitText(final CharSequence text, final int newCursorPosition) {
// TODO: the following is exceedingly error-prone. Right now when the cursor is in the
// middle of the composing word mComposingText only holds the part of the composing text
// that is before the cursor, so this actually works, but it's terribly confusing. Fix this.
mExpectedSelStart += text.length() - mComposingText.length();
mExpectedSelEnd = mExpectedSelStart;
if (hasCursorPosition()) {
mExpectedSelStart += text.length() - mComposingText.length();
mExpectedSelEnd = mExpectedSelStart;
}
mComposingText.setLength(0);
if (isConnected()) {
mTempObjectForCommitText.clear();
Expand Down Expand Up @@ -441,8 +442,10 @@ public void sendKeyEvent(final KeyEvent keyEvent) {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER:
mCommittedTextBeforeComposingText.append("\n");
mExpectedSelStart += 1;
mExpectedSelEnd = mExpectedSelStart;
if (hasCursorPosition()) {
mExpectedSelStart += 1;
mExpectedSelEnd = mExpectedSelStart;
}
break;
case KeyEvent.KEYCODE_DEL:
if (0 == mComposingText.length()) {
Expand All @@ -464,15 +467,19 @@ public void sendKeyEvent(final KeyEvent keyEvent) {
case KeyEvent.KEYCODE_UNKNOWN:
if (null != keyEvent.getCharacters()) {
mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
mExpectedSelStart += keyEvent.getCharacters().length();
mExpectedSelEnd = mExpectedSelStart;
if (hasCursorPosition()) {
mExpectedSelStart += keyEvent.getCharacters().length();
mExpectedSelEnd = mExpectedSelStart;
}
}
break;
default:
final String text = StringUtils.newSingleCodePointString(keyEvent.getUnicodeChar());
mCommittedTextBeforeComposingText.append(text);
mExpectedSelStart += text.length();
mExpectedSelEnd = mExpectedSelStart;
if (hasCursorPosition()) {
mExpectedSelStart += text.length();
mExpectedSelEnd = mExpectedSelStart;
}
break;
}
}
Expand Down Expand Up @@ -509,56 +516,6 @@ public boolean setSelection(final int start, final int end) {
return reloadTextCache();
}

/**
* Try to get the text from the editor to expose lies the framework may have been
* telling us. Concretely, when the device rotates and when the keyboard reopens in the same
* text field after having been closed with the back key, 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.
*/
public void tryFixLyingCursorPosition() {
mIC = mParent.getCurrentInputConnection();
final CharSequence textBeforeCursor = getTextBeforeCursor(
Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
final CharSequence selectedText = isConnected() ? mIC.getSelectedText(0 /* flags */) : null;
if (null == textBeforeCursor ||
(!TextUtils.isEmpty(selectedText) && mExpectedSelEnd == mExpectedSelStart)) {
// If textBeforeCursor is null, we have no idea what kind of text field we have or if
// thinking about the "cursor position" actually makes any sense. In this case we
// remember a meaningless cursor position. Contrast this with an empty string, which is
// valid and should mean the cursor is at the start of the text.
// Also, if we expect we don't have a selection but we DO have non-empty selected text,
// then the framework lied to us about the cursor position. In this case, we should just
// revert to the most basic behavior possible for the next action (backspace in
// particular comes to mind), so we remember a meaningless cursor position which should
// result in degraded behavior from the next input.
// Interestingly, in either case, chances are any action the user takes next will result
// in a call to onUpdateSelection, which should set things right.
mExpectedSelStart = mExpectedSelEnd = Constants.NOT_A_CURSOR_POSITION;
} else {
final int textLength = textBeforeCursor.length();
if (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE
&& (textLength > mExpectedSelStart
|| mExpectedSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
// It should not be possible to have only one of those variables be
// NOT_A_CURSOR_POSITION, so if they are equal, either the selection is zero-sized
// (simple cursor, no selection) or there is no cursor/we don't know its pos
final boolean wasEqual = mExpectedSelStart == mExpectedSelEnd;
mExpectedSelStart = textLength;
// We can't figure out the value of mLastSelectionEnd :(
// But at least if it's smaller than mLastSelectionStart something is wrong,
// and if they used to be equal we also don't want to make it look like there is a
// selection.
if (wasEqual || mExpectedSelStart > mExpectedSelEnd) {
mExpectedSelEnd = mExpectedSelStart;
}
}
}
}

public int getExpectedSelectionStart() {
return mExpectedSelStart;
}
Expand All @@ -573,4 +530,8 @@ public int getExpectedSelectionEnd() {
public boolean hasSelection() {
return mExpectedSelEnd != mExpectedSelStart;
}

public boolean hasCursorPosition() {
return mExpectedSelStart != INVALID_CURSOR_POSITION && mExpectedSelEnd != INVALID_CURSOR_POSITION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ public InputLogic(final LatinIME latinIME) {
public void startInput() {
mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once
mCurrentlyPressedHardwareKeys.clear();
// 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.
mConnection.tryFixLyingCursorPosition();
}

/**
Expand Down Expand Up @@ -576,7 +573,6 @@ public boolean retryResetCachesAndReturnSuccess(final boolean tryResumeSuggestio
// If remainingTries is 0, we should stop waiting for new tries, however we'll still
// return true as we need to perform other tasks (for example, loading the keyboard).
}
mConnection.tryFixLyingCursorPosition();
return true;
}
}

0 comments on commit 5c1e495

Please sign in to comment.