1
0
Fork 0

moved the hacks to their own package

This commit is contained in:
sspanak 2024-04-25 15:32:48 +03:00 committed by Dimo Karaivanov
parent b7565fd90a
commit 1bd1807a0d
8 changed files with 126 additions and 107 deletions

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.ime.helpers; package io.github.sspanak.tt9.hacks;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@ -6,122 +6,35 @@ import android.view.inputmethod.InputConnection;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.github.sspanak.tt9.ime.helpers.TextField;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.util.DeviceInfo;
public class AppHacks { public class AppHacks {
private final EditorInfo editorInfo; private final ConnectedAppInfo appInfo;
private final InputConnection inputConnection; private final InputConnection inputConnection;
private final SettingsStore settings; private final SettingsStore settings;
private final TextField textField; private final TextField textField;
private final InputType inputType;
public AppHacks(SettingsStore settings, InputConnection inputConnection, EditorInfo inputField, TextField textField) { public AppHacks(SettingsStore settings, InputConnection inputConnection, EditorInfo inputField, TextField textField) {
this.editorInfo = inputField;
this.inputConnection = inputConnection; this.inputConnection = inputConnection;
this.settings = settings; this.settings = settings;
this.textField = textField; this.textField = textField;
this.inputType = new InputType(inputConnection, inputField); appInfo = new ConnectedAppInfo(inputConnection, inputField);
} }
/** public ConnectedAppInfo getAppInfo() {
* isKindleInvertedTextField return appInfo;
* When sharing a document to the Amazon Kindle app. It displays a screen where one could edit the title and the author of the
* document. These two fields do not support SpannableString, which is used for suggestion highlighting. When they receive one
* weird side effects occur. Nevertheless, all other text fields in the app are fine, so we detect only these two particular ones.
*/
private boolean isKindleInvertedTextField() {
int titleImeOptions = EditorInfo.IME_ACTION_NONE | EditorInfo.IME_ACTION_SEND | EditorInfo.IME_FLAG_NAVIGATE_NEXT;
int titleAlternativeImeOptions = titleImeOptions | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; // sometimes the title field is different for no reason
int authorImeOptions = EditorInfo.IME_ACTION_SEND | EditorInfo.IME_ACTION_GO | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
return
isAppField("com.amazon.kindle", EditorInfo.TYPE_CLASS_TEXT)
&& (
editorInfo.imeOptions == titleImeOptions
|| editorInfo.imeOptions == titleAlternativeImeOptions
|| editorInfo.imeOptions == authorImeOptions
);
} }
/**
* isTermux
* Termux is a terminal emulator and it naturally has a text input, but it incorrectly introduces itself as having a NULL input,
* instead of a plain text input. However NULL inputs are usually, buttons and dropdown menus, which indeed can not read text
* and are ignored by TT9 by default. In order not to ignore Termux, we need this.
*/
public boolean isTermux() {
return isAppField("com.termux", EditorInfo.TYPE_NULL) && editorInfo.fieldId > 0;
}
/**
* isMessenger
* Facebook Messenger has flaky support for sending messages. To fix that, we detect the chat input field and send the appropriate
* key codes to it. See "onFbMessengerEnter()" for info how the hack works.
*/
private boolean isMessenger() {
return isAppField(
"com.facebook.orca",
EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
);
}
private boolean isMultilineTextInNonSystemApp() {
return editorInfo != null && !editorInfo.packageName.contains("android") && inputType.isMultilineText();
}
private boolean isGoogleChat() {
return isAppField(
"com.google.android.apps.dynamite",
EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT
);
}
/**
* Simulate the behavior of the Sonim native keyboard. In search fields with integrated lists,
* ENTER is used to select an item from the list. But some of them have actionId = NEXT, instead of NONE,
* which normally means "navigate to the next button or field". This hack correctly allows selection
* of the item, instead of performing navigation.
*/
private boolean isSonimSearchField(int action) {
return
DeviceInfo.isSonim() &&
editorInfo != null && (editorInfo.packageName.startsWith("com.android") || editorInfo.packageName.startsWith("com.sonim"))
&& (editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION) == action
&& (
inputType.isText()
// in some apps, they forgot to set the TEXT type, but fortunately, they did set the NO_SUGGESTIONS flag.
|| ((editorInfo.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) == EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS)
);
}
/**
* isAppField
* Detects a particular input field of a particular application.
*/
private boolean isAppField(String appPackageName, int fieldSpec) {
return
editorInfo != null
&& ((editorInfo.inputType & fieldSpec) == fieldSpec)
&& editorInfo.packageName.equals(appPackageName);
}
/** /**
* setComposingTextWithHighlightedStem * setComposingTextWithHighlightedStem
* A compatibility function for text fields that do not support SpannableString. Effectively disables highlighting. * A compatibility function for text fields that do not support SpannableString. Effectively disables highlighting.
*/ */
public void setComposingTextWithHighlightedStem(@NonNull String word, InputMode inputMode) { public void setComposingTextWithHighlightedStem(@NonNull String word, InputMode inputMode) {
if (isKindleInvertedTextField()) { if (appInfo.isKindleInvertedTextField()) {
textField.setComposingText(word); textField.setComposingText(word);
} else { } else {
textField.setComposingTextWithHighlightedStem(word, inputMode); textField.setComposingTextWithHighlightedStem(word, inputMode);
@ -135,9 +48,9 @@ public class AppHacks {
* returned, you must not attempt to delete text. This function has already done everything necessary. * returned, you must not attempt to delete text. This function has already done everything necessary.
*/ */
public boolean onBackspace(InputMode inputMode) { public boolean onBackspace(InputMode inputMode) {
if (isKindleInvertedTextField()) { if (appInfo.isKindleInvertedTextField()) {
inputMode.clearWordStem(); inputMode.clearWordStem();
} else if (isTermux()) { } else if (appInfo.isTermux()) {
return false; return false;
} }
@ -152,7 +65,7 @@ public class AppHacks {
* Returns "true" if the action was handled, "false" otherwise. * Returns "true" if the action was handled, "false" otherwise.
*/ */
public boolean onAction(int action) { public boolean onAction(int action) {
if (isSonimSearchField(action)) { if (appInfo.isSonimSearchField(action)) {
return sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER); return sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
} }
@ -167,11 +80,11 @@ public class AppHacks {
* it does nothing and return "false", signaling the system we have ignored the key press. * it does nothing and return "false", signaling the system we have ignored the key press.
*/ */
public boolean onEnter() { public boolean onEnter() {
if (settings.getFbMessengerHack() && isMessenger()) { if (settings.getFbMessengerHack() && appInfo.isMessenger()) {
return onEnterFbMessenger(); return onEnterFbMessenger();
} else if (settings.getGoogleChatHack() && isGoogleChat()) { } else if (settings.getGoogleChatHack() && appInfo.isGoogleChat()) {
return onEnterGoogleChat(); return onEnterGoogleChat();
} else if (isTermux() || isMultilineTextInNonSystemApp()) { } else if (appInfo.isTermux() || appInfo.isMultilineTextInNonSystemApp()) {
// Termux supports only ENTER, so we convert DPAD_CENTER for it. // Termux supports only ENTER, so we convert DPAD_CENTER for it.
// Any extra installed apps are likely not designed for hardware keypads, so again, // Any extra installed apps are likely not designed for hardware keypads, so again,
// we don't want to send DPAD_CENTER to them. // we don't want to send DPAD_CENTER to them.

View file

@ -0,0 +1,106 @@
package io.github.sspanak.tt9.hacks;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import io.github.sspanak.tt9.ime.helpers.InputType;
public class ConnectedAppInfo {
final EditorInfo editorInfo;
final InputType inputType;
ConnectedAppInfo(InputConnection inputConnection, EditorInfo inputField) {
this.editorInfo = inputField;
this.inputType = new InputType(inputConnection, inputField);
}
/**
* isKindleInvertedTextField
* When sharing a document to the Amazon Kindle app. It displays a screen where one could edit the title and the author of the
* document. These two fields do not support SpannableString, which is used for suggestion highlighting. When they receive one
* weird side effects occur. Nevertheless, all other text fields in the app are fine, so we detect only these two particular ones.
*/
boolean isKindleInvertedTextField() {
int titleImeOptions = EditorInfo.IME_ACTION_NONE | EditorInfo.IME_ACTION_SEND | EditorInfo.IME_FLAG_NAVIGATE_NEXT;
int titleAlternativeImeOptions = titleImeOptions | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; // sometimes the title field is different for no reason
int authorImeOptions = EditorInfo.IME_ACTION_SEND | EditorInfo.IME_ACTION_GO | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
return
isAppField("com.amazon.kindle", EditorInfo.TYPE_CLASS_TEXT)
&& (
editorInfo.imeOptions == titleImeOptions
|| editorInfo.imeOptions == titleAlternativeImeOptions
|| editorInfo.imeOptions == authorImeOptions
);
}
/**
* isTermux
* Termux is a terminal emulator and it naturally has a text input, but it incorrectly introduces itself as having a NULL input,
* instead of a plain text input. However NULL inputs are usually, buttons and dropdown menus, which indeed can not read text
* and are ignored by TT9 by default. In order not to ignore Termux, we need this.
*/
public boolean isTermux() {
return isAppField("com.termux", EditorInfo.TYPE_NULL) && editorInfo.fieldId > 0;
}
/**
* isMessenger
* Facebook Messenger has flaky support for sending messages. To fix that, we detect the chat input field and send the appropriate
* key codes to it. See "onFbMessengerEnter()" for info how the hack works.
*/
boolean isMessenger() {
return isAppField(
"com.facebook.orca",
EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
);
}
boolean isMultilineTextInNonSystemApp() {
return editorInfo != null && !editorInfo.packageName.contains("android") && inputType.isMultilineText();
}
boolean isGoogleChat() {
return isAppField(
"com.google.android.apps.dynamite",
EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT
);
}
/**
* Simulate the behavior of the Sonim native keyboard. In search fields with integrated lists,
* ENTER is used to select an item from the list. But some of them have actionId = NEXT, instead of NONE,
* which normally means "navigate to the next button or field". This hack correctly allows selection
* of the item, instead of performing navigation.
*/
boolean isSonimSearchField(int action) {
return
DeviceInfo.isSonim() &&
editorInfo != null && (editorInfo.packageName.startsWith("com.android") || editorInfo.packageName.startsWith("com.sonim"))
&& (editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION) == action
&& (
inputType.isText()
// in some apps, they forgot to set the TEXT type, but fortunately, they did set the NO_SUGGESTIONS flag.
|| ((editorInfo.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) == EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS)
);
}
/**
* isAppField
* Detects a particular input field of a particular application.
*/
boolean isAppField(String appPackageName, int fieldSpec) {
return
editorInfo != null
&& ((editorInfo.inputType & fieldSpec) == fieldSpec)
&& editorInfo.packageName.equals(appPackageName);
}
}

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.util; package io.github.sspanak.tt9.hacks;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;

View file

@ -15,6 +15,7 @@ import androidx.annotation.NonNull;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryLoader; import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.db.WordStoreAsync; import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.ime.modes.ModePredictive; import io.github.sspanak.tt9.ime.modes.ModePredictive;
import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.preferences.settings.SettingsStore;
@ -22,7 +23,6 @@ import io.github.sspanak.tt9.ui.UI;
import io.github.sspanak.tt9.ui.dialogs.PopupDialog; import io.github.sspanak.tt9.ui.dialogs.PopupDialog;
import io.github.sspanak.tt9.ui.main.MainView; import io.github.sspanak.tt9.ui.main.MainView;
import io.github.sspanak.tt9.ui.tray.StatusBar; import io.github.sspanak.tt9.ui.tray.StatusBar;
import io.github.sspanak.tt9.util.DeviceInfo;
import io.github.sspanak.tt9.util.Logger; import io.github.sspanak.tt9.util.Logger;
public class TraditionalT9 extends MainViewOps { public class TraditionalT9 extends MainViewOps {

View file

@ -11,7 +11,7 @@ import java.util.ArrayList;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryLoader; import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.ime.helpers.AppHacks; import io.github.sspanak.tt9.hacks.AppHacks;
import io.github.sspanak.tt9.ime.helpers.InputModeValidator; import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
import io.github.sspanak.tt9.ime.helpers.InputType; import io.github.sspanak.tt9.ime.helpers.InputType;
import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.ime.helpers.TextField;
@ -229,7 +229,7 @@ public abstract class TypingHandler extends KeyPadHandler {
* We do not want to handle any of these, hence we pass through all input to the system. * We do not want to handle any of these, hence we pass through all input to the system.
*/ */
protected int getInputModeId() { protected int getInputModeId() {
if (!inputType.isValid() || (inputType.isLimited() && !appHacks.isTermux())) { if (!inputType.isValid() || (inputType.isLimited() && !appHacks.getAppInfo().isTermux())) {
return InputMode.MODE_PASSTHROUGH; return InputMode.MODE_PASSTHROUGH;
} }

View file

@ -4,10 +4,10 @@ import androidx.preference.SwitchPreferenceCompat;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exporter.LogcatExporter; import io.github.sspanak.tt9.db.exporter.LogcatExporter;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemText; import io.github.sspanak.tt9.preferences.items.ItemText;
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment; import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
import io.github.sspanak.tt9.util.DeviceInfo;
public class DebugScreen extends BaseScreenFragment { public class DebugScreen extends BaseScreenFragment {
public static final String NAME = "Debug"; public static final String NAME = "Debug";

View file

@ -3,8 +3,8 @@ package io.github.sspanak.tt9.preferences.settings;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.screens.debug.ItemInputHandlingMode; import io.github.sspanak.tt9.preferences.screens.debug.ItemInputHandlingMode;
import io.github.sspanak.tt9.util.DeviceInfo;
import io.github.sspanak.tt9.util.Logger; import io.github.sspanak.tt9.util.Logger;
class SettingsHacks extends BaseSettings { class SettingsHacks extends BaseSettings {

View file

@ -5,7 +5,7 @@ import android.content.res.Configuration;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import io.github.sspanak.tt9.util.DeviceInfo; import io.github.sspanak.tt9.hacks.DeviceInfo;
public class SettingsUI extends SettingsTyping { public class SettingsUI extends SettingsTyping {
public final static int FONT_SIZE_DEFAULT = 0; public final static int FONT_SIZE_DEFAULT = 0;