From 90ae3cc387530229e5aca32c00d35495ab680e21 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 14 Jun 2023 15:37:47 +0800 Subject: [PATCH] Improve IM synchronization on Android * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): Reimplement as an InputConnection, not BaseInputConnection. * src/androidterm.c (performEditorAction): Sync prior to sending keyboard events. --- java/org/gnu/emacs/EmacsInputConnection.java | 189 ++++++++++++++++--- src/androidterm.c | 7 + 2 files changed, 174 insertions(+), 22 deletions(-) diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 73c93c67ac7..1bcc9a62a81 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -19,26 +19,35 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import android.view.inputmethod.BaseInputConnection; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; + +import android.view.KeyEvent; + import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextAttribute; import android.view.inputmethod.TextSnapshot; -import android.view.KeyEvent; - -import android.os.Build; - import android.util.Log; /* Android input methods, take number six. See textconv.c for more details; this is more-or-less a thin wrapper around that file. */ -public final class EmacsInputConnection extends BaseInputConnection +public final class EmacsInputConnection implements InputConnection { private static final String TAG = "EmacsInputConnection"; + + /* View associated with this input connection. */ private EmacsView view; + + /* The handle ID associated with that view's window. */ private short windowHandle; /* Whether or not to synchronize and call `updateIC' with the @@ -77,15 +86,18 @@ public final class EmacsInputConnection extends BaseInputConnection extractAbsoluteOffsets = true; }; + public EmacsInputConnection (EmacsView view) { - super (view, true); - this.view = view; this.windowHandle = view.window.handle; } + + /* The functions below are called by input methods whenever they + need to perform an edit. */ + @Override public boolean beginBatchEdit () @@ -116,7 +128,6 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } - @Override public boolean commitCompletion (CompletionInfo info) { @@ -133,6 +144,19 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public boolean + commitCorrection (CorrectionInfo info) + { + /* The input method calls this function not to commit text, but to + indicate that a subsequent edit will consist of a correction. + Emacs has no use for this information. + + Of course this completely contradicts the provided + documentation, but this is how Android actually behaves. */ + return false; + } + @Override public boolean commitText (CharSequence text, int newCursorPosition) @@ -170,6 +194,14 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public boolean + commitText (CharSequence text, int newCursorPosition, + TextAttribute textAttribute) + { + return commitText (text, newCursorPosition); + } + @Override public boolean deleteSurroundingText (int leftLength, int rightLength) @@ -187,6 +219,16 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public boolean + deleteSurroundingTextInCodePoints (int leftLength, int rightLength) + { + /* Emacs returns characters which cannot be represented in a Java + `char' as NULL characters, so code points always reflect + characters themselves. */ + return deleteSurroundingText (leftLength, rightLength); + } + @Override public boolean finishComposingText () @@ -277,6 +319,14 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public boolean + setComposingText (CharSequence text, int newCursorPosition, + TextAttribute textAttribute) + { + return setComposingText (text, newCursorPosition); + } + @Override public boolean setComposingRegion (int start, int end) @@ -292,6 +342,13 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public boolean + setComposingRegion (int start, int end, TextAttribute textAttribute) + { + return setComposingRegion (start, end); + } + @Override public boolean performEditorAction (int editorAction) @@ -430,6 +487,8 @@ public final class EmacsInputConnection extends BaseInputConnection } @Override + /* ACTION_MULTIPLE is apparently obsolete. */ + @SuppressWarnings ("deprecation") public boolean sendKeyEvent (KeyEvent key) { @@ -440,20 +499,33 @@ public final class EmacsInputConnection extends BaseInputConnection if (EmacsService.DEBUG_IC) Log.d (TAG, "sendKeyEvent: " + key); - return super.sendKeyEvent (key); - } + /* Use the standard API if possible. */ - @Override - public boolean - deleteSurroundingTextInCodePoints (int beforeLength, int afterLength) - { - /* Return if the input connection is out of date. */ - if (view.icSerial < view.icGeneration) - return false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + view.imManager.dispatchKeyEventFromInputMethod (view, key); + else + { + /* Fall back to dispatching the event manually if not. */ + + switch (key.getAction ()) + { + case KeyEvent.ACTION_DOWN: + view.onKeyDown (key.getKeyCode (), key); + break; + + case KeyEvent.ACTION_UP: + view.onKeyUp (key.getKeyCode (), key); + break; + + case KeyEvent.ACTION_MULTIPLE: + view.onKeyMultiple (key.getKeyCode (), + key.getRepeatCount (), + key); + break; + } + } - /* This can be implemented the same way as - deleteSurroundingText. */ - return this.deleteSurroundingText (beforeLength, afterLength); + return true; } @Override @@ -471,6 +543,16 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public boolean + requestCursorUpdates (int cursorUpdateMode, int filter) + { + if (filter != 0) + return false; + + return requestCursorUpdates (cursorUpdateMode); + } + @Override public SurroundingText getSurroundingText (int beforeLength, int afterLength, @@ -505,11 +587,74 @@ public final class EmacsInputConnection extends BaseInputConnection /* Override functions which are not implemented. */ + @Override + public Handler + getHandler () + { + return null; + } + + @Override + public void + closeConnection () + { + + } + + @Override + public boolean + commitContent (InputContentInfo inputContentInfo, int flags, + Bundle opts) + { + return false; + } + + @Override + public boolean + setImeConsumesInput (boolean imeConsumesInput) + { + return false; + } + @Override public TextSnapshot takeSnapshot () { - Log.d (TAG, "takeSnapshot"); return null; } + + @Override + public boolean + clearMetaKeyStates (int states) + { + return false; + } + + @Override + public boolean + reportFullscreenMode (boolean enabled) + { + return false; + } + + @Override + public boolean + performSpellCheck () + { + return false; + } + + @Override + public boolean + performPrivateCommand (String action, Bundle data) + { + return false; + } + + @Override + public int + getCursorCapsMode (int reqModes) + { + return 0; + } } diff --git a/src/androidterm.c b/src/androidterm.c index f08536c02ab..191ff65199b 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -5193,6 +5193,13 @@ NATIVE_NAME (performEditorAction) (JNIEnv *env, jobject object, union android_event event; + /* It's a good idea to call `android_sync_edit' before sending the + key event. Otherwise, if RET causes the current window to be + changed, any text previously committed might end up in the newly + selected window. */ + + android_sync_edit (); + /* Undocumented behavior: performEditorAction is apparently expected to finish composing any text. */ -- 2.39.2