From 5c1e49598965ccaccaeb1ecd9b4a4ee6222d5eff Mon Sep 17 00:00:00 2001 From: Raimondas Rimkus Date: Sun, 24 May 2020 10:28:17 +0300 Subject: [PATCH] Index based cursor move and delete swipe fallback for indexless inputs --- app/build.gradle | 2 +- .../inputmethod/latin/LatinIME.java | 34 +++++--- .../latin/RichInputConnection.java | 85 +++++-------------- .../latin/inputlogic/InputLogic.java | 4 - 4 files changed, 44 insertions(+), 81 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8699ce7b7..cf2eb1138 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion 19 targetSdkVersion 29 versionCode 69 - versionName "3.19" + versionName "3.20" } buildTypes { release { diff --git a/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/LatinIME.java b/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/LatinIME.java index 0ac50b8d1..fadef62ed 100644 --- a/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/LatinIME.java +++ b/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/LatinIME.java @@ -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 { @@ -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 diff --git a/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/RichInputConnection.java b/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/RichInputConnection.java index ecd34fa2f..cd85d769e 100644 --- a/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/RichInputConnection.java +++ b/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/RichInputConnection.java @@ -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; @@ -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. @@ -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(); @@ -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()) { @@ -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; } } @@ -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; } @@ -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; + } } diff --git a/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/inputlogic/InputLogic.java b/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/inputlogic/InputLogic.java index 37c0c9755..79fbe5021 100644 --- a/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/inputlogic/InputLogic.java @@ -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(); } /** @@ -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; } }