diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java index 9912077f..52ec8d7d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/AbstractHandler.java @@ -19,7 +19,7 @@ abstract public class AbstractHandler extends InputMethodService { // helpers abstract public SettingsStore getSettings(); abstract protected void onInit(); - abstract protected void onStart(InputConnection inputConnection, EditorInfo inputField); + abstract protected boolean onStart(InputConnection inputConnection, EditorInfo inputField); abstract protected void onFinishTyping(); abstract protected void onStop(); abstract protected void setInputField(InputConnection inputConnection, EditorInfo inputField); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java index a55cf9bd..3e06d2bf 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java @@ -29,9 +29,9 @@ public abstract class HotkeyHandler extends TypingHandler { @Override - protected void onStart(InputConnection connection, EditorInfo field) { - super.onStart(connection, field); + protected boolean onStart(InputConnection connection, EditorInfo field) { isSystemRTL = LanguageKind.isRTL(LanguageCollection.getDefault(this)); + return super.onStart(connection, field); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java index c7f18d35..20a62394 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java @@ -13,6 +13,7 @@ import androidx.annotation.NonNull; import io.github.sspanak.tt9.db.DictionaryLoader; import io.github.sspanak.tt9.db.WordStoreAsync; +import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.ModePassthrough; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.ui.UI; @@ -85,22 +86,22 @@ public class TraditionalT9 extends HotkeyHandler { @Override - protected void onStart(InputConnection connection, EditorInfo field) { - Logger.setLevel(settings.getLogLevel()); - - super.onStart(connection, field); - - if (mInputMode.isPassthrough()) { - // When the input is invalid or simple, let Android handle it. - onStop(); - updateInputViewShown(); - return; + protected boolean onStart(InputConnection connection, EditorInfo field) { + if (!super.onStart(connection, field)) { + return false; } - normalizationHandler.removeCallbacksAndMessages(null); + Logger.setLevel(settings.getLogLevel()); + + if (mInputMode.isPassthrough()) { + onStop(); + } else { + normalizationHandler.removeCallbacksAndMessages(null); + initUi(); + } - initUi(); updateInputViewShown(); + return true; } @@ -154,11 +155,11 @@ public class TraditionalT9 extends HotkeyHandler { * Some applications may hide our window and it remains invisible until the screen is touched or OK is pressed. * This is fine for touchscreen keyboards, but the hardware keyboard allows typing even when the window and the suggestions * are invisible. This function forces the InputMethodManager to show our window. - * WARNING! While this is running, it is not possible to load or display suggestions, - * or change the composing text. Use with care, after all processing is done. + * WARNING! Calling this may cause a restart, which will cause InputMode to be recreated. Depending + * on how much time the restart takes, this may erase the current user input. */ protected void forceShowWindowIfHidden() { - if (getInputMode().isPassthrough() || isInputViewShown() || settings.isMainLayoutStealth()) { + if (getInputModeId() == InputMode.MODE_PASSTHROUGH || isInputViewShown() || settings.isMainLayoutStealth()) { return; } @@ -188,7 +189,7 @@ public class TraditionalT9 extends HotkeyHandler { @Override protected boolean shouldBeVisible() { - return !getInputMode().isPassthrough(); + return getInputModeId() != InputMode.MODE_PASSTHROUGH; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java index 79f3be2d..7c368549 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java @@ -20,7 +20,6 @@ import io.github.sspanak.tt9.ime.modes.ModePredictive; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.ui.UI; -import io.github.sspanak.tt9.util.Logger; import io.github.sspanak.tt9.util.Text; public abstract class TypingHandler extends KeyPadHandler { @@ -46,7 +45,13 @@ public abstract class TypingHandler extends KeyPadHandler { } @Override - protected void onStart(InputConnection connection, EditorInfo field) { + protected boolean onStart(InputConnection connection, EditorInfo field) { + // ignore multiple calls for the same field, caused by showWindow() + // or weirdly functioning apps, such as the Qin SMS app + if (textField.equals(connection, field)) { + return false; + } + setInputField(connection, field); // in case we are back from Settings screen, update the language list @@ -58,10 +63,11 @@ public abstract class TypingHandler extends KeyPadHandler { mInputMode = getInputMode(); determineTextCase(); - suggestionOps.setTextField(textField); suggestionOps.set(null); appHacks = new AppHacks(settings, connection, field, textField); + + return true; } @@ -69,6 +75,7 @@ public abstract class TypingHandler extends KeyPadHandler { currentInputConnection = connection; inputType = new InputType(currentInputConnection, field); textField = new TextField(currentInputConnection, field); + suggestionOps.setTextField(textField); } @@ -84,6 +91,7 @@ public abstract class TypingHandler extends KeyPadHandler { protected void onFinishTyping() { suggestionOps.cancelDelayedAccept(); mInputMode = InputMode.getInstance(null, null, null, InputMode.MODE_PASSTHROUGH); + setInputField(null, null); } @@ -94,7 +102,6 @@ public abstract class TypingHandler extends KeyPadHandler { // 3. Some app may need special treatment, so let it be. boolean noTextBeforeCursor = textField.getStringBeforeCursor(1).isEmpty(); if (mInputMode.isPassthrough() || appHacks.onBackspace(mInputMode) || noTextBeforeCursor) { - Logger.d("onBackspace", "backspace ignored"); mInputMode.reset(); return false; } @@ -109,7 +116,6 @@ public abstract class TypingHandler extends KeyPadHandler { super.sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); } - Logger.d("onBackspace", "backspace handled"); return true; } @@ -207,18 +213,28 @@ public abstract class TypingHandler extends KeyPadHandler { /** - * getInputMode - * Load the last input mode or choose a more appropriate one. - * Some input fields support only numbers or are not suited for predictions (e.g. password fields) + * getInputModeId + * Return the last input mode ID or choose a more appropriate one. + * Some input fields support only numbers or are not suited for predictions (e.g. password fields). + * Others do not support text retrieval or composing text, or the AppHacks detected them as incompatible with us. + * We do not want to handle any of these, hence we pass through all input to the system. */ - protected InputMode getInputMode() { + protected int getInputModeId() { if (!inputType.isValid() || (inputType.isLimited() && !appHacks.isTermux())) { - return InputMode.getInstance(settings, mLanguage, inputType, InputMode.MODE_PASSTHROUGH); + return InputMode.MODE_PASSTHROUGH; } allowedInputModes = textField.determineInputModes(inputType); - int validModeId = InputModeValidator.validateMode(settings.getInputMode(), allowedInputModes); - return InputMode.getInstance(settings, mLanguage, inputType, validModeId); + return InputModeValidator.validateMode(settings.getInputMode(), allowedInputModes); + } + + + /** + * getInputMode + * Same as getInputModeId(), but returns an actual InputMode. + */ + protected InputMode getInputMode() { + return InputMode.getInstance(settings, mLanguage, inputType, getInputModeId()); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java index 955f4153..0f09ec2c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java @@ -34,6 +34,13 @@ public class TextField { } + public boolean equals(InputConnection inputConnection, EditorInfo inputField) { + return + connection != null && connection == inputConnection + && field != null && field == inputField; + } + + public boolean isThereText() { if (connection == null) { return false;