From 363e293cc919ab02c40bd9a8fa4875c2e5644b2d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 15 Jun 2023 12:36:50 +0800 Subject: [PATCH] Update Android port * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection, beginBatchEdit, reset, endBatchEdit): Keep track of the number of batch edits and return an appropriate value. (takeSnapshot): Implement function. * java/org/gnu/emacs/EmacsNative.java (takeSnapshot): New function. * java/org/gnu/emacs/EmacsService.java (resetIC): Improve debugging output. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Call `reset' to clear the UI side batch edit count. * src/androidterm.c (struct android_get_surrounding_text_context): New fields `conversion_start' and `conversion_end'. (android_get_surrounding_text): Return the conversion region. (android_get_surrounding_text_internal, NATIVE_NAME): Factor out `getSurroundingText'. (takeSnapshot): New function. --- java/org/gnu/emacs/EmacsInputConnection.java | 66 ++++++++-- java/org/gnu/emacs/EmacsNative.java | 2 + java/org/gnu/emacs/EmacsService.java | 25 +++- java/org/gnu/emacs/EmacsView.java | 3 + src/androidterm.c | 129 +++++++++++++++++-- 5 files changed, 201 insertions(+), 24 deletions(-) diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 1bcc9a62a81..f8dce5dfa79 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -50,6 +50,11 @@ public final class EmacsInputConnection implements InputConnection /* The handle ID associated with that view's window. */ private short windowHandle; + /* Number of batch edits currently underway. Used to avoid + synchronizing with the Emacs thread after each + `endBatchEdit'. */ + private int batchEditCount; + /* Whether or not to synchronize and call `updateIC' with the selection position after committing text. @@ -110,6 +115,10 @@ public final class EmacsInputConnection implements InputConnection Log.d (TAG, "beginBatchEdit"); EmacsNative.beginBatchEdit (windowHandle); + + /* Keep a record of the number of outstanding batch edits here as + well. */ + batchEditCount++; return true; } @@ -125,7 +134,14 @@ public final class EmacsInputConnection implements InputConnection Log.d (TAG, "endBatchEdit"); EmacsNative.endBatchEdit (windowHandle); - return true; + + /* Subtract one from the UI thread record of the number of batch + edits currently under way. */ + + if (batchEditCount > 0) + batchEditCount -= 1; + + return batchEditCount > 0; } public boolean @@ -584,21 +600,50 @@ public final class EmacsInputConnection implements InputConnection return text; } - - /* Override functions which are not implemented. */ - @Override - public Handler - getHandler () + public TextSnapshot + takeSnapshot () { - return null; + TextSnapshot snapshot; + + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return null; + + snapshot = EmacsNative.takeSnapshot (windowHandle); + + if (EmacsService.DEBUG_IC) + Log.d (TAG, ("takeSnapshot: " + + snapshot.getSurroundingText ().getText () + + " @ " + snapshot.getCompositionEnd () + + ", " + snapshot.getCompositionStart ())); + + return snapshot; } @Override public void closeConnection () { + batchEditCount = 0; + } + + + public void + reset () + { + batchEditCount = 0; + } + + + /* Override functions which are not implemented. */ + + @Override + public Handler + getHandler () + { + return null; } @Override @@ -616,13 +661,6 @@ public final class EmacsInputConnection implements InputConnection return false; } - @Override - public TextSnapshot - takeSnapshot () - { - return null; - } - @Override public boolean clearMetaKeyStates (int states) diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 2fcbf8b94ef..9e87c419f95 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -26,6 +26,7 @@ import android.graphics.Bitmap; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextSnapshot; public final class EmacsNative { @@ -230,6 +231,7 @@ public final class EmacsNative public static native SurroundingText getSurroundingText (short window, int left, int right, int flags); + public static native TextSnapshot takeSnapshot (short window); /* Return the current value of the selection, or -1 upon diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 96216e51cf4..2fe4e8c4146 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -767,8 +767,31 @@ public final class EmacsService extends Service public void resetIC (EmacsWindow window, int icMode) { + int oldMode; + if (DEBUG_IC) - Log.d (TAG, "resetIC: " + window); + Log.d (TAG, "resetIC: " + window + ", " + icMode); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && (oldMode = window.view.getICMode ()) == icMode + /* Don't do this if there is currently no input + connection. */ + && oldMode != IC_MODE_NULL) + { + if (DEBUG_IC) + Log.d (TAG, "resetIC: calling invalidateInput"); + + /* Android 33 and later allow the IM reset to be optimized out + and replaced by a call to `invalidateInput', which is much + faster, as it does not involve resetting the input + connection. */ + + icBeginSynchronous (); + window.view.imManager.invalidateInput (window.view); + icEndSynchronous (); + + return; + } window.view.setICMode (icMode); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 278c6025902..aba1184b0c2 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -681,6 +681,9 @@ public final class EmacsView extends ViewGroup if (inputConnection == null) inputConnection = new EmacsInputConnection (this); + else + /* Clear several pieces of state in the input connection. */ + inputConnection.reset (); /* Return the input connection. */ return inputConnection; diff --git a/src/androidterm.c b/src/androidterm.c index 191ff65199b..29076981a47 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -5668,6 +5668,10 @@ struct android_get_surrounding_text_context /* Offsets into that text. */ ptrdiff_t offset, start, end; + /* The start and end indices of the conversion region. + -1 if it does not exist. */ + ptrdiff_t conversion_start, conversion_end; + /* The window. */ android_window window; }; @@ -5706,22 +5710,47 @@ android_get_surrounding_text (void *data) request->start = request->end; request->end = temp; } + + /* Retrieve the conversion region. */ + + request->conversion_start = -1; + request->conversion_end = -1; + + if (MARKERP (f->conversion.compose_region_start)) + { + request->conversion_start + = marker_position (f->conversion.compose_region_start) - 1; + request->conversion_end + = marker_position (f->conversion.compose_region_end) - 1; + } } -JNIEXPORT jobject JNICALL -NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject ignored_object, - jshort window, jint before_length, - jint after_length, jint flags) -{ - JNI_STACK_ALIGNMENT_PROLOGUE; +/* Return a local reference to a `SurroundingText' object describing + WINDOW's surrounding text. ENV should be a valid JNI environment + for the current thread. - static jclass class; - static jmethodID constructor; + BEFORE_LENGTH and AFTER_LENGTH specify the number of characters + around point and mark to return. + Return the conversion region (or -1) in *CONVERSION_START and + *CONVERSION_END if non-NULL. + + Value is the object upon success, else NULL. */ + +static jobject +android_get_surrounding_text_internal (JNIEnv *env, jshort window, + jint before_length, + jint after_length, + ptrdiff_t *conversion_start, + ptrdiff_t *conversion_end) +{ struct android_get_surrounding_text_context context; jstring string; jobject object; + static jclass class; + static jmethodID constructor; + /* Initialize CLASS if it has not yet been initialized. */ if (!class) @@ -5745,7 +5774,9 @@ NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject ignored_object, class = (*env)->NewGlobalRef (env, class); if (!class) - return NULL; + /* Clear class to prevent a local reference from remaining in + `class'. */ + return (class = NULL); /* Now look for its constructor. */ constructor = (*env)->GetMethodID (env, class, "", @@ -5787,6 +5818,86 @@ NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject ignored_object, if (!object) return NULL; + /* Now return the conversion region if that was requested. */ + + if (conversion_start) + { + *conversion_start = context.conversion_start; + *conversion_end = context.conversion_start; + } + + return object; +} + +JNIEXPORT jobject JNICALL +NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject object, + jshort window, jint before_length, + jint after_length, jint flags) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + return android_get_surrounding_text_internal (env, window, before_length, + after_length, NULL, NULL); +} + +JNIEXPORT jobject JNICALL +NATIVE_NAME (takeSnapshot) (JNIEnv *env, jobject object, jshort window) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + jobject text; + ptrdiff_t start, end; + + static jclass class; + static jmethodID constructor; + + /* First, obtain the surrounding text and conversion region. */ + text = android_get_surrounding_text_internal (env, window, 600, 600, + &start, &end); + + /* If that fails, return NULL. */ + + if (!text) + return NULL; + + /* Next, initialize the TextSnapshot class. */ + + if (!class) + { + class + = (*env)->FindClass (env, ("android/view/inputmethod" + "/TextSnapshot")); +#if __ANDROID_API__ < 33 + /* If CLASS cannot be found, the version of Android currently + running is too old. */ + + if (!class) + { + (*env)->ExceptionClear (env); + return NULL; + } +#else /* __ANDROID_API__ >= 33 */ + assert (class); +#endif /* __ANDROID_API__ < 33 */ + + class = (*env)->NewGlobalRef (env, class); + if (!class) + /* Clear class to prevent a local reference from remaining in + `class'. */ + return (class = NULL); + + constructor = (*env)->GetMethodID (env, class, "", + "(Landroid/view/inputmethod" + "/SurroundingText;III)V"); + assert (constructor); + } + + /* Try to create a TextSnapshot object. */ + eassert (start <= end); + object = (*env)->NewObject (env, class, constructor, text, + (jint) min (start, TYPE_MAXIMUM (jint)), + (jint) min (end, TYPE_MAXIMUM (jint)), + (jint) 0); return object; } -- 2.39.2