1
0
Fork 0

Improved OK/Enter key handling

* pass through the key press to the system when there is nothing to do

  * respect the app request for: DONE action + NO_ENTER flag and send the appropriate one, instead of always sending ENTER
This commit is contained in:
sspanak 2024-03-28 11:28:46 +02:00 committed by Dimo Karaivanov
parent 25d62bba43
commit 1edda847b7
5 changed files with 59 additions and 74 deletions

View file

@ -45,11 +45,11 @@ public abstract class HotkeyHandler extends TypingHandler {
if (suggestionOps.isEmpty()) {
int action = textField.getAction();
return action == TextField.IME_ACTION_ENTER ? appHacks.onEnter() : textField.performAction(action);
}
} else {
onAcceptSuggestionManually(suggestionOps.acceptCurrent(), KeyEvent.KEYCODE_ENTER);
return true;
}
}
public boolean onHotkey(int keyCode, boolean repeat, boolean validateOnly) {

View file

@ -18,8 +18,6 @@ abstract class KeyPadHandler extends AbstractHandler {
private final static String DEBOUNCE_TIMER = "debounce_";
// temporal key handling
private boolean isBackspaceHandled = false;
private int ignoreNextKeyUp = 0;
private int lastKeyCode = 0;
@ -78,7 +76,7 @@ abstract class KeyPadHandler extends AbstractHandler {
public void onStartInput(EditorInfo inputField, boolean restarting) {
Logger.d(
"KeyPadHandler",
"===> Start Up; packageName: " + inputField.packageName + " inputType: " + inputField.inputType + " fieldId: " + inputField.fieldId + " fieldName: " + inputField.fieldName + " privateImeOptions: " + inputField.privateImeOptions + " imeOptions: " + inputField.imeOptions + " extras: " + inputField.extras
"===> Start Up; packageName: " + inputField.packageName + " inputType: " + inputField.inputType + " actionId: " + inputField.actionId + " imeOptions: " + inputField.imeOptions + " privateImeOptions: " + inputField.privateImeOptions + " extras: " + inputField.extras
);
onStart(getCurrentInputConnection(), inputField);
}
@ -133,9 +131,9 @@ abstract class KeyPadHandler extends AbstractHandler {
// "backspace" key must repeat its function when held down, so we handle it in a special way
if (Key.isBackspace(settings, keyCode)) {
if (onBackspace()) {
return isBackspaceHandled = true;
return Key.setHandled(KeyEvent.KEYCODE_DEL, true);
} else {
isBackspaceHandled = false;
Key.setHandled(KeyEvent.KEYCODE_DEL, false);
}
}
@ -153,7 +151,7 @@ abstract class KeyPadHandler extends AbstractHandler {
}
return
Key.isOK(keyCode)
Key.setHandled(KeyEvent.KEYCODE_ENTER, Key.isOK(keyCode) && onOK())
|| handleHotkey(keyCode, true, false, true) // hold a hotkey, handled in onKeyLongPress())
|| handleHotkey(keyCode, false, keyRepeatCounter + 1 > 0, true) // press a hotkey, handled in onKeyUp()
|| Key.isPoundOrStar(keyCode) && onText(String.valueOf((char) event.getUnicodeChar()), true)
@ -227,7 +225,7 @@ abstract class KeyPadHandler extends AbstractHandler {
return true;
}
if (Key.isBackspace(settings, keyCode) && isBackspaceHandled) {
if (Key.isBackspace(settings, keyCode) && Key.isHandled(KeyEvent.KEYCODE_DEL)) {
return true;
}
@ -245,7 +243,7 @@ abstract class KeyPadHandler extends AbstractHandler {
}
return
Key.isOK(keyCode) && onOK()
(Key.isOK(KeyEvent.KEYCODE_ENTER) && Key.isHandled(keyCode))
|| handleHotkey(keyCode, false, keyRepeatCounter > 0, false)
|| Key.isPoundOrStar(keyCode) && onText(String.valueOf((char) event.getUnicodeChar()), false)
|| super.onKeyUp(keyCode, event); // let the system handle the keys we don't care about (usually, the touch "buttons")

View file

@ -59,6 +59,13 @@ public class AppHacks {
}
private boolean isMultilineTextField() {
return
editorInfo != null
&& (editorInfo.inputType & TextField.TYPE_MULTILINE_TEXT) == TextField.TYPE_MULTILINE_TEXT;
}
private boolean isGoogleChat() {
return isAppField(
"com.google.android.apps.dynamite",
@ -111,80 +118,46 @@ public class AppHacks {
/**
* onEnter
* Tries to guess and send the correct confirmation key code or sequence of key codes, depending on the connected application
* and input field. On invalid connection or field, it does nothing.
* This hack applies to all applications, not only selected ones.
* Tries to guess and send the correct confirmation key code or sequence of key codes,
* depending on the connected application and input field. On invalid connection or field,
* it does nothing.
*/
public boolean onEnter() {
if (isTermux()) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
return true;
} else if (isMessenger()) {
} else if (settings.getFbMessengerHack() && isMessenger()) {
return onEnterFbMessenger();
} else if (isGoogleChat()) {
} else if (settings.getGoogleChatHack() && isGoogleChat()) {
return onEnterGoogleChat();
} else if (isMultilineTextField()) {
return onEnterMultilineText();
}
return onEnterDefault();
return false;
}
/**
* onEnterDefault
* This is the default "ENTER" routine for most applications that support send-with-enter functionality. It will attempt to
* guess and send the correct confirmation key code, be it "ENTER" or "DPAD_CENTER".
* On invalid textField, it does nothing.
* In generic text fields, or when no hacks are in effect, we just type a new line,
* as one would expect when pressing ENTER.
*/
private boolean onEnterDefault() {
if (textField == null) {
return false;
}
String oldText = textField.getStringBeforeCursor() + textField.getStringAfterCursor();
sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
// If there is no text, there is nothing to send, so there is no need to attempt any hacks.
// We just pass through DPAD_CENTER and finish as if the key press was handled by the system.
if (oldText.isEmpty()) {
return true;
}
try {
// In Android there is no strictly defined confirmation key, hence DPAD_CENTER may have done nothing.
// If so, send an alternative key code as a final resort.
Thread.sleep(80);
String newText = textField.getStringBeforeCursor() + textField.getStringAfterCursor();
if (newText.equals(oldText)) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
}
} catch (InterruptedException e) {
// This thread got interrupted. Assume it's because the connected application has taken an action
// after receiving DPAD_CENTER, so we don't need to do anything else.
return true;
}
return true;
private boolean onEnterMultilineText() {
return inputConnection != null && inputConnection.commitText("\n", 1);
}
/**
* onEnterFbMessenger
* Messenger responds only to ENTER, but not DPAD_CENTER, so we make sure to send the correct code,
* no matter how the hardware key is implemented. In case the hack is disabled, we just type a new line,
* as one would expect.
* no matter how the hardware key is implemented.
*/
private boolean onEnterFbMessenger() {
if (inputConnection == null || textField == null || !textField.isThereText()) {
return false;
}
if (settings.getFbMessengerHack()) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
} else {
// in case the setting is disabled, just type a new line as one would expect
inputConnection.commitText("\n", 1);
}
return true;
}
@ -193,14 +166,12 @@ public class AppHacks {
* onEnterGoogleChat
* Google Chat does not seem to respond consistently to ENTER. So we trick it by selecting
* the send button it, then going back to the text field, so that one can continue typing.
* If the hack is disabled, we just type a new line.
*/
private boolean onEnterGoogleChat() {
if (inputConnection == null || textField == null || !textField.isThereText()) {
return false;
}
if (settings.getGoogleChatHack()) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
@ -208,9 +179,6 @@ public class AppHacks {
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true);
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true);
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true);
} else {
inputConnection.commitText("\n", 1);
}
return true;
}

View file

@ -2,9 +2,25 @@ package io.github.sspanak.tt9.ime.helpers;
import android.view.KeyEvent;
import java.util.HashMap;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class Key {
private static final HashMap<Integer, Boolean> handledKeys = new HashMap<>();
public static boolean setHandled(int keyCode, boolean handled) {
handledKeys.put(keyCode, handled);
return handled;
}
public static boolean isHandled(int keyCode) {
return Boolean.TRUE.equals(handledKeys.get(keyCode));
}
public static boolean isBackspace(SettingsStore settings, int keyCode) {
return
keyCode == KeyEvent.KEYCODE_DEL

View file

@ -21,10 +21,12 @@ import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.util.Text;
public class TextField {
public static final int TYPE_MULTILINE_TEXT = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
public static final int IME_ACTION_ENTER = EditorInfo.IME_MASK_ACTION + 1;
public final InputConnection connection;
public final EditorInfo field;
private final InputConnection connection;
private final EditorInfo field;
public TextField(InputConnection inputConnection, EditorInfo inputField) {
connection = inputConnection;
@ -342,6 +344,7 @@ public class TextField {
int standardAction = field.imeOptions & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
switch (standardAction) {
case EditorInfo.IME_ACTION_GO:
case EditorInfo.IME_ACTION_DONE:
case EditorInfo.IME_ACTION_NEXT:
case EditorInfo.IME_ACTION_PREVIOUS:
case EditorInfo.IME_ACTION_SEARCH: