10th attempt to fix the privileged options problem: InputConnection is no longer cached within the app
This commit is contained in:
parent
9c63e66a1d
commit
903e756dc0
10 changed files with 104 additions and 56 deletions
|
|
@ -1,8 +1,10 @@
|
||||||
package io.github.sspanak.tt9.hacks;
|
package io.github.sspanak.tt9.hacks;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.ime.helpers.StandardInputType;
|
import io.github.sspanak.tt9.ime.helpers.StandardInputType;
|
||||||
import io.github.sspanak.tt9.util.sys.DeviceInfo;
|
import io.github.sspanak.tt9.util.sys.DeviceInfo;
|
||||||
|
|
@ -10,9 +12,9 @@ import io.github.sspanak.tt9.util.sys.DeviceInfo;
|
||||||
public class InputType extends StandardInputType {
|
public class InputType extends StandardInputType {
|
||||||
private final boolean isUs;
|
private final boolean isUs;
|
||||||
|
|
||||||
public InputType(Context context, InputConnection inputConnection, EditorInfo inputField) {
|
public InputType(@Nullable InputMethodService ims, EditorInfo inputField) {
|
||||||
super(inputConnection, inputField);
|
super(ims, inputField);
|
||||||
isUs = isAppField(context != null ? context.getPackageName() : "", EditorInfo.TYPE_NULL);
|
isUs = isAppField(ims != null ? ims.getPackageName() : null, EditorInfo.TYPE_NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package io.github.sspanak.tt9.ime;
|
||||||
|
|
||||||
import android.inputmethodservice.InputMethodService;
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
|
||||||
|
|
||||||
import io.github.sspanak.tt9.ime.helpers.SuggestionOps;
|
import io.github.sspanak.tt9.ime.helpers.SuggestionOps;
|
||||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||||
|
|
@ -19,10 +18,10 @@ abstract public class AbstractHandler extends InputMethodService {
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
abstract protected void onInit();
|
abstract protected void onInit();
|
||||||
abstract protected boolean onStart(InputConnection inputConnection, EditorInfo inputField);
|
abstract protected boolean onStart(EditorInfo inputField);
|
||||||
abstract protected void onFinishTyping();
|
abstract protected void onFinishTyping();
|
||||||
abstract protected void onStop();
|
abstract protected void onStop();
|
||||||
abstract protected void setInputField(InputConnection inputConnection, EditorInfo inputField);
|
abstract protected void setInputField(EditorInfo inputField);
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
abstract protected void createSuggestionBar();
|
abstract protected void createSuggestionBar();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package io.github.sspanak.tt9.ime;
|
package io.github.sspanak.tt9.ime;
|
||||||
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
@ -37,10 +36,10 @@ abstract public class MainViewHandler extends HotkeyHandler {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onStart(InputConnection connection, EditorInfo field) {
|
protected boolean onStart(EditorInfo field) {
|
||||||
resetNormalizedDimensions();
|
resetNormalizedDimensions();
|
||||||
dragResize = settings.getDragResize();
|
dragResize = settings.getDragResize();
|
||||||
return super.onStart(connection, field);
|
return super.onStart(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
|
@ -39,7 +38,7 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInputField(getCurrentInputConnection(), getCurrentInputEditorInfo());
|
setInputField(getCurrentInputEditorInfo());
|
||||||
return shouldBeVisible();
|
return shouldBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,13 +79,13 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
LOG_TAG,
|
LOG_TAG,
|
||||||
"===> Start Up; packageName: " + inputField.packageName + " inputType: " + inputField.inputType + " actionId: " + inputField.actionId + " imeOptions: " + inputField.imeOptions + " privateImeOptions: " + inputField.privateImeOptions + " 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(inputField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartInputView(EditorInfo inputField, boolean restarting) {
|
public void onStartInputView(EditorInfo inputField, boolean restarting) {
|
||||||
onStart(getCurrentInputConnection(), inputField);
|
onStart(inputField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -130,13 +129,13 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onStart(InputConnection connection, EditorInfo field) {
|
protected boolean onStart(EditorInfo field) {
|
||||||
if (zombieChecks == 0 && !SystemSettings.isTT9Selected(this)) {
|
if (zombieChecks == 0 && !SystemSettings.isTT9Selected(this)) {
|
||||||
startZombieCheck();
|
startZombieCheck();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDead || !super.onStart(connection, field)) {
|
if (isDead || !super.onStart(field)) {
|
||||||
setStatusIcon(mInputMode, mLanguage);
|
setStatusIcon(mInputMode, mLanguage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +150,7 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
initUi(mInputMode);
|
initUi(mInputMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
InputType newInputType = new InputType(getApplicationContext(), connection, field);
|
InputType newInputType = new InputType(this, field);
|
||||||
|
|
||||||
if (newInputType.isText()) {
|
if (newInputType.isText()) {
|
||||||
DataStore.loadWordPairs(DictionaryLoader.getInstance(this), LanguageCollection.getAll(settings.getEnabledLanguageIds()));
|
DataStore.loadWordPairs(DictionaryLoader.getInstance(this), LanguageCollection.getAll(settings.getEnabledLanguageIds()));
|
||||||
|
|
@ -241,7 +240,7 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
|
|
||||||
protected void cleanUp() {
|
protected void cleanUp() {
|
||||||
super.cleanUp();
|
super.cleanUp();
|
||||||
setInputField(null, null);
|
setInputField(null);
|
||||||
backgroundTasks.removeCallbacksAndMessages(null);
|
backgroundTasks.removeCallbacksAndMessages(null);
|
||||||
zombieChecks = SettingsStore.ZOMBIE_CHECK_MAX;
|
zombieChecks = SettingsStore.ZOMBIE_CHECK_MAX;
|
||||||
zombieDetector.removeCallbacksAndMessages(null);
|
zombieDetector.removeCallbacksAndMessages(null);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package io.github.sspanak.tt9.ime;
|
package io.github.sspanak.tt9.ime;
|
||||||
|
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
@ -29,10 +29,10 @@ import io.github.sspanak.tt9.util.Text;
|
||||||
public abstract class TypingHandler extends KeyPadHandler {
|
public abstract class TypingHandler extends KeyPadHandler {
|
||||||
// internal settings/data
|
// internal settings/data
|
||||||
@NonNull protected AppHacks appHacks = new AppHacks(null, null, null);
|
@NonNull protected AppHacks appHacks = new AppHacks(null, null, null);
|
||||||
@NonNull protected InputType inputType = new InputType(null, null, null);
|
@NonNull protected InputType inputType = new InputType(null, null);
|
||||||
@NonNull protected TextField textField = new TextField(null, null, null);
|
@NonNull protected TextField textField = new TextField(null, null, null);
|
||||||
@NonNull protected TextSelection textSelection = new TextSelection(this,null);
|
@NonNull protected TextSelection textSelection = new TextSelection(null);
|
||||||
@NonNull protected SuggestionOps suggestionOps = new SuggestionOps(null, null, null, null, null);
|
@NonNull protected SuggestionOps suggestionOps = new SuggestionOps(null, null, null, null, null, null);
|
||||||
|
|
||||||
// input
|
// input
|
||||||
@NonNull protected ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
@NonNull protected ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
||||||
|
|
@ -44,7 +44,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
|
|
||||||
|
|
||||||
protected void createSuggestionBar() {
|
protected void createSuggestionBar() {
|
||||||
suggestionOps = new SuggestionOps(settings, mainView, textField, this::onAcceptSuggestionsDelayed, this::onOK);
|
suggestionOps = new SuggestionOps(this, settings, mainView, textField, this::onAcceptSuggestionsDelayed, this::onOK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,10 +53,10 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onStart(InputConnection connection, EditorInfo field) {
|
protected boolean onStart(EditorInfo field) {
|
||||||
boolean restart = textField.equals(connection, field);
|
boolean restart = textField.equals(getCurrentInputConnection(), field);
|
||||||
|
|
||||||
setInputField(connection, field);
|
setInputField(field);
|
||||||
|
|
||||||
// 1. In case we are back from Settings screen, update the language list
|
// 1. In case we are back from Settings screen, update the language list
|
||||||
// 2. If the connected app hints it is in a language different than the current one,
|
// 2. If the connected app hints it is in a language different than the current one,
|
||||||
|
|
@ -79,14 +79,15 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void setInputField(InputConnection connection, EditorInfo field) {
|
protected void setInputField(EditorInfo field) {
|
||||||
if (textField.equals(connection, field)) {
|
if (textField.equals(getCurrentInputConnection(), field)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inputType = new InputType(getApplicationContext(), connection, field);
|
InputMethodService context = field != null ? this : null;
|
||||||
textField = new TextField(settings, connection, field);
|
inputType = new InputType(context, field);
|
||||||
textSelection = new TextSelection(this, connection);
|
textField = new TextField(context, settings, field);
|
||||||
|
textSelection = new TextSelection(context);
|
||||||
|
|
||||||
// changing the TextField and notifying all interested classes is an atomic operation
|
// changing the TextField and notifying all interested classes is an atomic operation
|
||||||
appHacks = new AppHacks(inputType, textField, textSelection);
|
appHacks = new AppHacks(inputType, textField, textSelection);
|
||||||
|
|
@ -105,7 +106,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
protected void onFinishTyping() {
|
protected void onFinishTyping() {
|
||||||
suggestionOps.cancelDelayedAccept();
|
suggestionOps.cancelDelayedAccept();
|
||||||
mInputMode = InputMode.getInstance(null, null, null, null, InputMode.MODE_PASSTHROUGH);
|
mInputMode = InputMode.getInstance(null, null, null, null, InputMode.MODE_PASSTHROUGH);
|
||||||
setInputField(null, null);
|
setInputField(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package io.github.sspanak.tt9.ime.helpers;
|
package io.github.sspanak.tt9.ime.helpers;
|
||||||
|
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
|
|
||||||
|
|
@ -14,19 +15,25 @@ import io.github.sspanak.tt9.util.sys.DeviceInfo;
|
||||||
public class InputField {
|
public class InputField {
|
||||||
public static final int IME_ACTION_ENTER = EditorInfo.IME_MASK_ACTION + 1;
|
public static final int IME_ACTION_ENTER = EditorInfo.IME_MASK_ACTION + 1;
|
||||||
|
|
||||||
@Nullable protected final InputConnection connection;
|
@Nullable protected final InputMethodService ims;
|
||||||
@Nullable protected final EditorInfo field;
|
@Nullable protected final EditorInfo field;
|
||||||
|
|
||||||
|
|
||||||
protected InputField(@Nullable InputConnection inputConnection, @Nullable EditorInfo inputField) {
|
protected InputField(@Nullable InputMethodService ims, @Nullable EditorInfo inputField) {
|
||||||
connection = inputConnection;
|
this.ims = ims;
|
||||||
field = inputField;
|
field = inputField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected InputConnection getConnection() {
|
||||||
|
return ims != null ? ims.getCurrentInputConnection() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean equals(InputConnection inputConnection, EditorInfo inputField) {
|
public boolean equals(InputConnection inputConnection, EditorInfo inputField) {
|
||||||
return
|
return
|
||||||
connection != null && connection == inputConnection
|
inputConnection != null && inputConnection == getConnection()
|
||||||
&& field != null && field == inputField;
|
&& field != null && field == inputField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +80,7 @@ public class InputField {
|
||||||
* Note that it is up to the app to decide what to do or ignore the action ID.
|
* Note that it is up to the app to decide what to do or ignore the action ID.
|
||||||
*/
|
*/
|
||||||
public boolean performAction(int actionId) {
|
public boolean performAction(int actionId) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
return connection != null && actionId != EditorInfo.IME_ACTION_NONE && connection.performEditorAction(actionId);
|
return connection != null && actionId != EditorInfo.IME_ACTION_NONE && connection.performEditorAction(actionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package io.github.sspanak.tt9.ime.helpers;
|
package io.github.sspanak.tt9.ime.helpers;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -16,18 +18,24 @@ import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||||
abstract public class StandardInputType {
|
abstract public class StandardInputType {
|
||||||
private static final int TYPE_MULTILINE_TEXT = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
|
private static final int TYPE_MULTILINE_TEXT = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
|
||||||
|
|
||||||
protected final InputConnection connection;
|
@Nullable protected final InputMethodService ims;
|
||||||
protected final EditorInfo field;
|
protected final EditorInfo field;
|
||||||
|
|
||||||
|
|
||||||
protected StandardInputType(InputConnection inputConnection, EditorInfo inputField) {
|
protected StandardInputType(@Nullable InputMethodService ims, EditorInfo inputField) {
|
||||||
connection = inputConnection;
|
this.ims = ims;
|
||||||
field = inputField;
|
field = inputField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected InputConnection getConnection() {
|
||||||
|
return ims != null ? ims.getCurrentInputConnection() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return field != null && connection != null;
|
return field != null && getConnection() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -193,7 +201,7 @@ abstract public class StandardInputType {
|
||||||
* editor state.
|
* editor state.
|
||||||
*/
|
*/
|
||||||
public int determineTextCase() {
|
public int determineTextCase() {
|
||||||
if (connection == null || field == null || field.inputType == InputType.TYPE_NULL) {
|
if (getConnection() == null || field == null || field.inputType == InputType.TYPE_NULL) {
|
||||||
return InputMode.CASE_UNDEFINED;
|
return InputMode.CASE_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package io.github.sspanak.tt9.ime.helpers;
|
package io.github.sspanak.tt9.ime.helpers;
|
||||||
|
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
|
|
@ -25,11 +26,11 @@ public class SuggestionOps {
|
||||||
@NonNull private TextField textField;
|
@NonNull private TextField textField;
|
||||||
|
|
||||||
|
|
||||||
public SuggestionOps(@Nullable SettingsStore settings, @Nullable ResizableMainView mainView, @Nullable TextField textField, @Nullable ConsumerCompat<String> onDelayedAccept, @Nullable Runnable onSuggestionClick) {
|
public SuggestionOps(@Nullable InputMethodService ims, @Nullable SettingsStore settings, @Nullable ResizableMainView mainView, @Nullable TextField textField, @Nullable ConsumerCompat<String> onDelayedAccept, @Nullable Runnable onSuggestionClick) {
|
||||||
delayedAcceptHandler = new Handler(Looper.getMainLooper());
|
delayedAcceptHandler = new Handler(Looper.getMainLooper());
|
||||||
this.onDelayedAccept = onDelayedAccept != null ? onDelayedAccept : s -> {};
|
this.onDelayedAccept = onDelayedAccept != null ? onDelayedAccept : s -> {};
|
||||||
|
|
||||||
this.textField = textField != null ? textField : new TextField(null, null, null);
|
this.textField = textField != null ? textField : new TextField(ims, null, null);
|
||||||
|
|
||||||
if (settings != null && mainView != null && onSuggestionClick != null) {
|
if (settings != null && mainView != null && onSuggestionClick != null) {
|
||||||
suggestionBar = new SuggestionsBar(settings, mainView, onSuggestionClick);
|
suggestionBar = new SuggestionsBar(settings, mainView, onSuggestionClick);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package io.github.sspanak.tt9.ime.helpers;
|
package io.github.sspanak.tt9.ime.helpers;
|
||||||
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
|
@ -10,6 +11,7 @@ import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.hacks.InputType;
|
import io.github.sspanak.tt9.hacks.InputType;
|
||||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||||
|
|
@ -25,10 +27,10 @@ public class TextField extends InputField {
|
||||||
private final boolean isNonText;
|
private final boolean isNonText;
|
||||||
|
|
||||||
|
|
||||||
public TextField(SettingsStore settings, InputConnection inputConnection, EditorInfo inputField) {
|
public TextField(@Nullable InputMethodService ims, SettingsStore settings, EditorInfo inputField) {
|
||||||
super(inputConnection, inputField);
|
super(ims, inputField);
|
||||||
|
|
||||||
InputType inputType = new InputType(null, inputConnection, inputField);
|
InputType inputType = new InputType(ims, inputField);
|
||||||
isComposingSupported = !inputType.isNumeric() && !inputType.isLimited() && !inputType.isRustDesk() && (settings == null || settings.getAllowComposingText());
|
isComposingSupported = !inputType.isNumeric() && !inputType.isLimited() && !inputType.isRustDesk() && (settings == null || settings.getAllowComposingText());
|
||||||
isNonText = !inputType.isText();
|
isNonText = !inputType.isText();
|
||||||
}
|
}
|
||||||
|
|
@ -40,12 +42,14 @@ public class TextField extends InputField {
|
||||||
|
|
||||||
|
|
||||||
@NonNull public String getStringAfterCursor(int numberOfChars) {
|
@NonNull public String getStringAfterCursor(int numberOfChars) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
CharSequence chars = connection != null && numberOfChars > 0 ? connection.getTextAfterCursor(numberOfChars, 0) : null;
|
CharSequence chars = connection != null && numberOfChars > 0 ? connection.getTextAfterCursor(numberOfChars, 0) : null;
|
||||||
return chars != null ? chars.toString() : "";
|
return chars != null ? chars.toString() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NonNull public String getStringBeforeCursor(int numberOfChars) {
|
@NonNull public String getStringBeforeCursor(int numberOfChars) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
CharSequence chars = connection != null && numberOfChars > 0 ? connection.getTextBeforeCursor(numberOfChars, 0) : null;
|
CharSequence chars = connection != null && numberOfChars > 0 ? connection.getTextBeforeCursor(numberOfChars, 0) : null;
|
||||||
return chars != null ? chars.toString() : "";
|
return chars != null ? chars.toString() : "";
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +137,7 @@ public class TextField extends InputField {
|
||||||
* "deleteSurroundingText()" to delete a region of text or a Unicode character.
|
* "deleteSurroundingText()" to delete a region of text or a Unicode character.
|
||||||
*/
|
*/
|
||||||
public void deleteChars(int numberOfChars) {
|
public void deleteChars(int numberOfChars) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (numberOfChars <= 0 || connection == null) {
|
if (numberOfChars <= 0 || connection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -162,6 +167,7 @@ public class TextField extends InputField {
|
||||||
* No action is taken when there is no such word.
|
* No action is taken when there is no such word.
|
||||||
*/
|
*/
|
||||||
public void deletePrecedingSpace(String word) {
|
public void deletePrecedingSpace(String word) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +193,7 @@ public class TextField extends InputField {
|
||||||
* there is no such word before the cursor.
|
* there is no such word before the cursor.
|
||||||
*/
|
*/
|
||||||
public void addPrecedingSpace(String word) {
|
public void addPrecedingSpace(String word) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -210,6 +217,7 @@ public class TextField extends InputField {
|
||||||
* the given "text". Returns "true" if the operation was successful, "false" otherwise.
|
* the given "text". Returns "true" if the operation was successful, "false" otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean recompose(String text) {
|
public boolean recompose(String text) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (text == null || connection == null || !isComposingSupported) {
|
if (text == null || connection == null || !isComposingSupported) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +235,7 @@ public class TextField extends InputField {
|
||||||
* A fail-safe setter that appends text to the field, ignoring NULL input.
|
* A fail-safe setter that appends text to the field, ignoring NULL input.
|
||||||
*/
|
*/
|
||||||
public void setText(String text) {
|
public void setText(String text) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (text != null && connection != null) {
|
if (text != null && connection != null) {
|
||||||
connection.commitText(text, 1);
|
connection.commitText(text, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -245,6 +254,7 @@ public class TextField extends InputField {
|
||||||
*/
|
*/
|
||||||
public void setComposingText(CharSequence text, int position) {
|
public void setComposingText(CharSequence text, int position) {
|
||||||
composingText = text;
|
composingText = text;
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (text != null && connection != null && isComposingSupported) {
|
if (text != null && connection != null && isComposingSupported) {
|
||||||
connection.setComposingText(text, position);
|
connection.setComposingText(text, position);
|
||||||
}
|
}
|
||||||
|
|
@ -275,6 +285,7 @@ public class TextField extends InputField {
|
||||||
* Finish composing text or do nothing if the text field is invalid.
|
* Finish composing text or do nothing if the text field is invalid.
|
||||||
*/
|
*/
|
||||||
public void finishComposingText() {
|
public void finishComposingText() {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -332,7 +343,7 @@ public class TextField extends InputField {
|
||||||
|
|
||||||
public boolean moveCursor(boolean backward) {
|
public boolean moveCursor(boolean backward) {
|
||||||
if (
|
if (
|
||||||
connection == null
|
getConnection() == null
|
||||||
|| (backward && getStringBeforeCursor(1).isEmpty())
|
|| (backward && getStringBeforeCursor(1).isEmpty())
|
||||||
|| (!backward && getStringAfterCursor(1).isEmpty())
|
|| (!backward && getStringAfterCursor(1).isEmpty())
|
||||||
) {
|
) {
|
||||||
|
|
@ -351,6 +362,7 @@ public class TextField extends InputField {
|
||||||
|
|
||||||
|
|
||||||
public boolean sendDownUpKeyEvents(int keyCode, boolean shift, boolean ctrl) {
|
public boolean sendDownUpKeyEvents(int keyCode, boolean shift, boolean ctrl) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
int metaState = shift ? KeyEvent.META_SHIFT_ON : 0;
|
int metaState = shift ? KeyEvent.META_SHIFT_ON : 0;
|
||||||
metaState |= ctrl ? KeyEvent.META_CTRL_ON : 0;
|
metaState |= ctrl ? KeyEvent.META_CTRL_ON : 0;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package io.github.sspanak.tt9.ime.helpers;
|
package io.github.sspanak.tt9.ime.helpers;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.view.inputmethod.ExtractedText;
|
import android.view.inputmethod.ExtractedText;
|
||||||
import android.view.inputmethod.ExtractedTextRequest;
|
import android.view.inputmethod.ExtractedTextRequest;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
|
|
@ -11,19 +11,22 @@ import androidx.annotation.Nullable;
|
||||||
import io.github.sspanak.tt9.util.sys.Clipboard;
|
import io.github.sspanak.tt9.util.sys.Clipboard;
|
||||||
|
|
||||||
public class TextSelection {
|
public class TextSelection {
|
||||||
@Nullable private final InputConnection connection;
|
@Nullable private final InputMethodService ims;
|
||||||
private final Context context;
|
|
||||||
private int currentStart = 0;
|
private int currentStart = 0;
|
||||||
private int currentEnd = 0;
|
private int currentEnd = 0;
|
||||||
|
|
||||||
|
|
||||||
public TextSelection(Context context, @Nullable InputConnection connection) {
|
public TextSelection(@Nullable InputMethodService ims) {
|
||||||
this.context = context;
|
this.ims = ims;
|
||||||
this.connection = connection;
|
|
||||||
detectCursorPosition();
|
detectCursorPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private InputConnection getConnection() {
|
||||||
|
return ims != null ? ims.getCurrentInputConnection() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void onSelectionUpdate(int start, int end) {
|
public void onSelectionUpdate(int start, int end) {
|
||||||
currentStart = start;
|
currentStart = start;
|
||||||
currentEnd = end;
|
currentEnd = end;
|
||||||
|
|
@ -36,6 +39,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.setSelection(currentEnd, currentEnd);
|
connection.setSelection(currentEnd, currentEnd);
|
||||||
}
|
}
|
||||||
|
|
@ -43,6 +47,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
public void clear(boolean backward) {
|
public void clear(boolean backward) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.setSelection(
|
connection.setSelection(
|
||||||
backward ? Math.min(currentStart, currentEnd) : Math.max(currentStart, currentEnd),
|
backward ? Math.min(currentStart, currentEnd) : Math.max(currentStart, currentEnd),
|
||||||
|
|
@ -58,6 +63,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
public void selectAll() {
|
public void selectAll() {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.performContextMenuAction(android.R.id.selectAll);
|
connection.performContextMenuAction(android.R.id.selectAll);
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +71,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
public void selectNextChar(boolean backward) {
|
public void selectNextChar(boolean backward) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.setSelection(currentStart, currentEnd + (backward ? -1 : 1));
|
connection.setSelection(currentStart, currentEnd + (backward ? -1 : 1));
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +79,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
public void selectNextWord(boolean backward) {
|
public void selectNextWord(boolean backward) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -81,12 +89,16 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
public boolean copy() {
|
public boolean copy() {
|
||||||
|
if (ims == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CharSequence selectedText = getSelectedText();
|
CharSequence selectedText = getSelectedText();
|
||||||
if (selectedText.length() == 0) {
|
if (selectedText.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Clipboard.copy(context, selectedText);
|
Clipboard.copy(ims, selectedText);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +111,11 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
public void paste(@NonNull TextField textField) {
|
public void paste(@NonNull TextField textField) {
|
||||||
String clipboardText = Clipboard.paste(context);
|
if (ims == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String clipboardText = Clipboard.paste(ims);
|
||||||
if (!clipboardText.isEmpty()) {
|
if (!clipboardText.isEmpty()) {
|
||||||
textField.setText(clipboardText);
|
textField.setText(clipboardText);
|
||||||
}
|
}
|
||||||
|
|
@ -107,6 +123,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
private int getNextWordPosition(boolean backward) {
|
private int getNextWordPosition(boolean backward) {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return currentEnd + (backward ? -1 : 1);
|
return currentEnd + (backward ? -1 : 1);
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +152,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
private void detectCursorPosition() {
|
private void detectCursorPosition() {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +166,7 @@ public class TextSelection {
|
||||||
|
|
||||||
|
|
||||||
private CharSequence getSelectedText() {
|
private CharSequence getSelectedText() {
|
||||||
|
InputConnection connection = getConnection();
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue