1
0
Fork 0

10th attempt to fix the privileged options problem: InputConnection is no longer cached within the app

This commit is contained in:
sspanak 2025-06-11 14:51:03 +03:00 committed by Dimo Karaivanov
parent 9c63e66a1d
commit 903e756dc0
10 changed files with 104 additions and 56 deletions

View file

@ -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);
} }

View file

@ -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();

View file

@ -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);
} }

View file

@ -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);

View file

@ -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);
} }

View file

@ -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);
} }

View file

@ -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;
} }

View file

@ -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);

View file

@ -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,7 +362,8 @@ public class TextField extends InputField {
public boolean sendDownUpKeyEvents(int keyCode, boolean shift, boolean ctrl) { public boolean sendDownUpKeyEvents(int keyCode, boolean shift, boolean ctrl) {
if (connection != null) { InputConnection connection = getConnection();
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;
KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, metaState); KeyEvent downEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0, metaState);

View file

@ -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 "";
} }