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()) { if (suggestionOps.isEmpty()) {
int action = textField.getAction(); int action = textField.getAction();
return action == TextField.IME_ACTION_ENTER ? appHacks.onEnter() : textField.performAction(action); return action == TextField.IME_ACTION_ENTER ? appHacks.onEnter() : textField.performAction(action);
} } else {
onAcceptSuggestionManually(suggestionOps.acceptCurrent(), KeyEvent.KEYCODE_ENTER); onAcceptSuggestionManually(suggestionOps.acceptCurrent(), KeyEvent.KEYCODE_ENTER);
return true; return true;
} }
}
public boolean onHotkey(int keyCode, boolean repeat, boolean validateOnly) { 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_"; private final static String DEBOUNCE_TIMER = "debounce_";
// temporal key handling // temporal key handling
private boolean isBackspaceHandled = false;
private int ignoreNextKeyUp = 0; private int ignoreNextKeyUp = 0;
private int lastKeyCode = 0; private int lastKeyCode = 0;
@ -78,7 +76,7 @@ abstract class KeyPadHandler extends AbstractHandler {
public void onStartInput(EditorInfo inputField, boolean restarting) { public void onStartInput(EditorInfo inputField, boolean restarting) {
Logger.d( Logger.d(
"KeyPadHandler", "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); 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 // "backspace" key must repeat its function when held down, so we handle it in a special way
if (Key.isBackspace(settings, keyCode)) { if (Key.isBackspace(settings, keyCode)) {
if (onBackspace()) { if (onBackspace()) {
return isBackspaceHandled = true; return Key.setHandled(KeyEvent.KEYCODE_DEL, true);
} else { } else {
isBackspaceHandled = false; Key.setHandled(KeyEvent.KEYCODE_DEL, false);
} }
} }
@ -153,7 +151,7 @@ abstract class KeyPadHandler extends AbstractHandler {
} }
return 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, true, false, true) // hold a hotkey, handled in onKeyLongPress())
|| handleHotkey(keyCode, false, keyRepeatCounter + 1 > 0, true) // press a hotkey, handled in onKeyUp() || handleHotkey(keyCode, false, keyRepeatCounter + 1 > 0, true) // press a hotkey, handled in onKeyUp()
|| Key.isPoundOrStar(keyCode) && onText(String.valueOf((char) event.getUnicodeChar()), true) || Key.isPoundOrStar(keyCode) && onText(String.valueOf((char) event.getUnicodeChar()), true)
@ -219,7 +217,7 @@ abstract class KeyPadHandler extends AbstractHandler {
return false; return false;
} }
// Logger.d("onKeyUp", "Key: " + keyCode + " repeat?: " + event.getRepeatCount()); // Logger.d("onKeyUp", "Key: " + keyCode + " repeat?: " + event.getRepeatCount());
if (keyCode == ignoreNextKeyUp) { if (keyCode == ignoreNextKeyUp) {
// Logger.d("onKeyUp", "Ignored: " + keyCode); // Logger.d("onKeyUp", "Ignored: " + keyCode);
@ -227,7 +225,7 @@ abstract class KeyPadHandler extends AbstractHandler {
return true; return true;
} }
if (Key.isBackspace(settings, keyCode) && isBackspaceHandled) { if (Key.isBackspace(settings, keyCode) && Key.isHandled(KeyEvent.KEYCODE_DEL)) {
return true; return true;
} }
@ -245,7 +243,7 @@ abstract class KeyPadHandler extends AbstractHandler {
} }
return return
Key.isOK(keyCode) && onOK() (Key.isOK(KeyEvent.KEYCODE_ENTER) && Key.isHandled(keyCode))
|| handleHotkey(keyCode, false, keyRepeatCounter > 0, false) || handleHotkey(keyCode, false, keyRepeatCounter > 0, false)
|| Key.isPoundOrStar(keyCode) && onText(String.valueOf((char) event.getUnicodeChar()), 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") || 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() { private boolean isGoogleChat() {
return isAppField( return isAppField(
"com.google.android.apps.dynamite", "com.google.android.apps.dynamite",
@ -111,80 +118,46 @@ public class AppHacks {
/** /**
* onEnter * onEnter
* Tries to guess and send the correct confirmation key code or sequence of key codes, depending on the connected application * Tries to guess and send the correct confirmation key code or sequence of key codes,
* and input field. On invalid connection or field, it does nothing. * depending on the connected application and input field. On invalid connection or field,
* This hack applies to all applications, not only selected ones. * it does nothing.
*/ */
public boolean onEnter() { public boolean onEnter() {
if (isTermux()) { if (isTermux()) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
return true; return true;
} else if (isMessenger()) { } else if (settings.getFbMessengerHack() && isMessenger()) {
return onEnterFbMessenger(); return onEnterFbMessenger();
} else if (isGoogleChat()) { } else if (settings.getGoogleChatHack() && isGoogleChat()) {
return onEnterGoogleChat(); return onEnterGoogleChat();
} else if (isMultilineTextField()) {
return onEnterMultilineText();
} }
return onEnterDefault(); return false;
} }
/** /**
* onEnterDefault * In generic text fields, or when no hacks are in effect, we just type a new line,
* This is the default "ENTER" routine for most applications that support send-with-enter functionality. It will attempt to * as one would expect when pressing ENTER.
* guess and send the correct confirmation key code, be it "ENTER" or "DPAD_CENTER".
* On invalid textField, it does nothing.
*/ */
private boolean onEnterDefault() { private boolean onEnterMultilineText() {
if (textField == null) { return inputConnection != null && inputConnection.commitText("\n", 1);
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;
} }
/** /**
* onEnterFbMessenger * onEnterFbMessenger
* Messenger responds only to ENTER, but not DPAD_CENTER, so we make sure to send the correct code, * 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, * no matter how the hardware key is implemented.
* as one would expect.
*/ */
private boolean onEnterFbMessenger() { private boolean onEnterFbMessenger() {
if (inputConnection == null || textField == null || !textField.isThereText()) { if (inputConnection == null || textField == null || !textField.isThereText()) {
return false; return false;
} }
if (settings.getFbMessengerHack()) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); 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; return true;
} }
@ -193,14 +166,12 @@ public class AppHacks {
* onEnterGoogleChat * onEnterGoogleChat
* Google Chat does not seem to respond consistently to ENTER. So we trick it by selecting * 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. * 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() { private boolean onEnterGoogleChat() {
if (inputConnection == null || textField == null || !textField.isThereText()) { if (inputConnection == null || textField == null || !textField.isThereText()) {
return false; return false;
} }
if (settings.getGoogleChatHack()) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); 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); sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true);
sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true); sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB, true);
} else {
inputConnection.commitText("\n", 1);
}
return true; return true;
} }

View file

@ -2,9 +2,25 @@ package io.github.sspanak.tt9.ime.helpers;
import android.view.KeyEvent; import android.view.KeyEvent;
import java.util.HashMap;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
public class Key { 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) { public static boolean isBackspace(SettingsStore settings, int keyCode) {
return return
keyCode == KeyEvent.KEYCODE_DEL keyCode == KeyEvent.KEYCODE_DEL

View file

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