Skip to content

Instantly share code, notes, and snippets.

@mzorz
Created July 26, 2019 19:15
Show Gist options
  • Save mzorz/54d3c5bc8ac2f979f8a8368b6bbdb23c to your computer and use it in GitHub Desktop.
Save mzorz/54d3c5bc8ac2f979f8a8368b6bbdb23c to your computer and use it in GitHub Desktop.
InputConnectionWrapper used for logging
package org.wordpress.aztec.ime;
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import org.wordpress.android.util.AppLog;
import org.wordpress.aztec.AztecText;
/**
* A class to implement the TextInput 'onKeyPress' API on android for soft keyboards. It is
* instantiated in {@link ReactEditText#onCreateInputConnection(EditorInfo)}.
*
* <p>Android IMEs interface with EditText views through the {@link InputConnection} interface, so
* any observable change in state of the EditText via the soft-keyboard, should be a side effect of
* one or more of the methods in {@link InputConnectionWrapper}.
*
* <p>{@link InputConnection#setComposingText(CharSequence, int)} is used to set the composing
* region (the underlined text) in the {@link android.widget.EditText} view, i.e. when React
* Native's TextInput has the property 'autoCorrect' set to true. When text is being composed in the
* composing state within the EditText, each key press will result in a call to {@link
* InputConnection#setComposingText(CharSequence, int)} with a CharSequence argument equal to that
* of the entire composing region, rather than a single character diff. We can reason about the
* keyPress based on the resultant cursor position changes of the EditText after applying this
* change. For example if the cursor moved backwards by one character when composing, it's likely it
* was a delete; if it moves forward by a character, likely to be a key press of that character.
*
* <p>IMEs can also call {@link InputConnection#beginBatchEdit()} to signify a batch of operations.
* One such example is committing a word currently in composing state with the press of the space
* key. It is IME dependent but the stock Android keyboard behavior seems to be to commit the
* currently composing text with {@link InputConnection#setComposingText(CharSequence, int)} and
* commits a space character with a separate call to {@link
* InputConnection#setComposingText(CharSequence, int)}. Here we chose to emit the last input of a
* batch edit as that tends to be the user input, but it's completely arbitrary.
*
* <p>Another function of this class is to detect backspaces when the cursor at the beginning of the
* {@link android.widget.EditText}, i.e no text is deleted.
*
* <p>N.B. this class is only applicable for soft keyboards behavior. For hardware keyboards {@link
* android.view.View#onKeyDown(int, KeyEvent)} can be overridden to obtain the keycode of the key
* pressed.
*/
public class AztecTextInputConnectionWrapper extends InputConnectionWrapper{
public static final String NEWLINE_RAW_VALUE = "\n";
public static final String BACKSPACE_KEY_VALUE = "Backspace";
public static final String ENTER_KEY_VALUE = "Enter";
private AztecText mEditText;
private boolean mIsBatchEdit;
private String mKey = null;
public AztecTextInputConnectionWrapper(
InputConnection target, final AztecText aztecText) {
super(target, false);
mEditText = aztecText;
AppLog.e(AppLog.T.EDITOR, "ATICW constructor");
}
@Override
public boolean beginBatchEdit() {
mIsBatchEdit = true;
AppLog.e(AppLog.T.EDITOR, "ATICW beginBatchEdit");
return super.beginBatchEdit();
}
@Override
public boolean endBatchEdit() {
mIsBatchEdit = false;
if (mKey != null) {
AppLog.e(AppLog.T.EDITOR, "ATICW endBatchEdit: " + mKey);
mKey = null;
} else {
AppLog.e(AppLog.T.EDITOR, "ATICW endBatchEdit: (null)");
}
return super.endBatchEdit();
}
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
int previousSelectionStart = mEditText.getSelectionStart();
int previousSelectionEnd = mEditText.getSelectionEnd();
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: newCursorPosition: " + newCursorPosition + " - text: " + text);
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: previousSelectionStart: " + previousSelectionStart + " - previousSelectionEnd: " + previousSelectionEnd);
String key;
boolean consumed = super.setComposingText(text, newCursorPosition);
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: consumed: " + consumed);
int currentSelectionStart = mEditText.getSelectionStart();
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: currentSelectionStart: " + currentSelectionStart);
boolean noPreviousSelection = previousSelectionStart == previousSelectionEnd;
boolean cursorDidNotMove = currentSelectionStart == previousSelectionStart;
boolean cursorMovedBackwardsOrAtBeginningOfInput =
(currentSelectionStart < previousSelectionStart) || currentSelectionStart <= 0;
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: noPreviousSelection: " + noPreviousSelection);
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: cursorDidNotMove: " + cursorDidNotMove);
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: cursorMovedBackwardsOrAtBeginningOfInput: " + cursorMovedBackwardsOrAtBeginningOfInput);
if (cursorMovedBackwardsOrAtBeginningOfInput || (!noPreviousSelection && cursorDidNotMove)) {
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: KEY is: (BACKSPACE)");
key = BACKSPACE_KEY_VALUE;
} else {
key = String.valueOf(mEditText.getText().charAt(currentSelectionStart - 1));
AppLog.e(AppLog.T.EDITOR, "ATICW setComposingText: KEY is: " + key);
}
return consumed;
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
String key = text.toString();
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: newCursorPosition: " + newCursorPosition + " - text: " + text + " - key.length(): " + key.length());
// FIXME TEST CODE
int previousSelectionStart = mEditText.getSelectionStart();
int previousSelectionEnd = mEditText.getSelectionEnd();
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: newCursorPosition: " + newCursorPosition + " - text: " + text);
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: previousSelectionStart: " + previousSelectionStart + " - previousSelectionEnd: " + previousSelectionEnd);
// Assume not a keyPress if length > 1 (or 2 if unicode)
if (key.length() <= 2) {
if (key.equals("")) {
key = BACKSPACE_KEY_VALUE;
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: assuming BACKSPACE_KEY_VALUE");
}
}
//return super.commitText(text, newCursorPosition);
boolean result = super.commitText(text, newCursorPosition);
int currentSelectionStart = mEditText.getSelectionStart();
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: currentSelectionStart: " + currentSelectionStart);
// FIXME test CORRECTOR
if (currentSelectionStart == previousSelectionStart && newCursorPosition > 0) {
// if cursor did not advance, despite newCursorPosition being different than zero, let's make it advance
// otherwise it will be left pointing to a wrong position
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: ABOUT TO SET SELECTION: " + previousSelectionStart+newCursorPosition);
mEditText.setSelection(previousSelectionStart+newCursorPosition);
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: AFTER SET SELECTION: " + mEditText.getSelectionStart());
}
boolean noPreviousSelection = previousSelectionStart == previousSelectionEnd;
boolean cursorDidNotMove = currentSelectionStart == previousSelectionStart;
boolean cursorMovedBackwardsOrAtBeginningOfInput =
(currentSelectionStart < previousSelectionStart) || currentSelectionStart <= 0;
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: noPreviousSelection: " + noPreviousSelection);
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: cursorDidNotMove: " + cursorDidNotMove);
AppLog.e(AppLog.T.EDITOR, "ATICW commitText: cursorMovedBackwardsOrAtBeginningOfInput: " + cursorMovedBackwardsOrAtBeginningOfInput);
return result;
}
// @Override
// public boolean commitText(CharSequence text, int newCursorPosition) {
// String key = text.toString();
// AppLog.e(AppLog.T.EDITOR, "ATICW commitText: newCursorPosition: " + newCursorPosition + " - text: " + text + " - key.length(): " + key.length());
//
// // Assume not a keyPress if length > 1 (or 2 if unicode)
// if (key.length() <= 2) {
// if (key.equals("")) {
// key = BACKSPACE_KEY_VALUE;
// AppLog.e(AppLog.T.EDITOR, "ATICW commitText: assuming BACKSPACE_KEY_VALUE");
// }
// dispatchKeyEventOrEnqueue(key);
// }
//
// return super.commitText(text, newCursorPosition);
// }
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
AppLog.e(AppLog.T.EDITOR, "ATICW deleteSurroundingText: beforeLength: " + beforeLength + " afterLength: " + afterLength);
return super.deleteSurroundingText(beforeLength, afterLength);
}
// Called by SwiftKey when cursor at beginning of input when there is a delete
// or when enter is pressed anywhere in the text. Whereas stock Android Keyboard calls
// {@link InputConnection#deleteSurroundingText} & {@link InputConnection#commitText}
// in each case, respectively.
@Override
public boolean sendKeyEvent(KeyEvent event) {
AppLog.e(AppLog.T.EDITOR, "ATICW sendKeyEvent: event: " + event);
return super.sendKeyEvent(event);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment