Update Android port

* java/org/gnu/emacs/EmacsInputConnection.java (beginBatchEdit)
(endBatchEdit, commitCompletion, commitText, deleteSurroundingText)
(finishComposingText, getSelectedText, getTextAfterCursor)
(getTextBeforeCursor, setComposingText, setComposingRegion)
(performEditorAction, performContextMenuAction, getExtractedText)
(setSelection, sendKeyEvent, deleteSurroundingTextInCodePoints)
(requestCursorUpdates): Ensure that the input connection is up
to date.
(getSurroundingText): New function.
* java/org/gnu/emacs/EmacsNative.java (getSurroundingText):
Export new C function.
* java/org/gnu/emacs/EmacsService.java (resetIC): Invalidate
previously created input connections.
* java/org/gnu/emacs/EmacsView.java (EmacsView)
(onCreateInputConnection): Signify that input connections are
now up to date.
* src/androidterm.c (struct
android_get_surrounding_text_context): New structure.
(android_get_surrounding_text, NATIVE_NAME):
* src/textconv.c (get_surrounding_text):
* src/textconv.h: New functions.
This commit is contained in:
Po Lu 2023-06-07 11:03:56 +08:00
parent 9a68041f2c
commit 63339a9577
7 changed files with 370 additions and 0 deletions

View file

@ -23,7 +23,9 @@
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextSnapshot;
import android.view.KeyEvent;
import android.os.Build;
@ -88,6 +90,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
beginBatchEdit ()
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "beginBatchEdit");
@ -99,6 +105,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
endBatchEdit ()
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "endBatchEdit");
@ -110,6 +120,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
commitCompletion (CompletionInfo info)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitCompletion: " + info);
@ -125,6 +139,10 @@ public final class EmacsInputConnection extends BaseInputConnection
{
int[] selection;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
@ -156,6 +174,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
deleteSurroundingText (int leftLength, int rightLength)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("deleteSurroundingText: "
+ leftLength + " " + rightLength));
@ -169,6 +191,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
finishComposingText ()
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "finishComposingText");
@ -180,6 +206,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public String
getSelectedText (int flags)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getSelectedText: " + flags);
@ -192,6 +222,10 @@ public final class EmacsInputConnection extends BaseInputConnection
{
String string;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
@ -210,6 +244,10 @@ public final class EmacsInputConnection extends BaseInputConnection
{
String string;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
@ -226,6 +264,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
setComposingText (CharSequence text, int newCursorPosition)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("setComposingText: "
+ text + " ## " + newCursorPosition));
@ -239,6 +281,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
setComposingRegion (int start, int end)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setComposingRegion: " + start + " " + end);
@ -250,6 +296,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
performEditorAction (int editorAction)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "performEditorAction: " + editorAction);
@ -263,6 +313,10 @@ public final class EmacsInputConnection extends BaseInputConnection
{
int action;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "performContextMenuAction: " + contextMenuAction);
@ -310,6 +364,10 @@ public final class EmacsInputConnection extends BaseInputConnection
ExtractedText text;
int[] selection;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: " + request.hintMaxChars + ", "
+ request.hintMaxLines + " " + flags);
@ -360,6 +418,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
setSelection (int start, int end)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setSelection: " + start + " " + end);
@ -371,6 +433,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
sendKeyEvent (KeyEvent key)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "sendKeyEvent: " + key);
@ -381,6 +447,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
deleteSurroundingTextInCodePoints (int beforeLength, int afterLength)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
/* This can be implemented the same way as
deleteSurroundingText. */
return this.deleteSurroundingText (beforeLength, afterLength);
@ -390,6 +460,10 @@ public final class EmacsInputConnection extends BaseInputConnection
public boolean
requestCursorUpdates (int cursorUpdateMode)
{
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return false;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "requestCursorUpdates: " + cursorUpdateMode);
@ -397,6 +471,37 @@ public final class EmacsInputConnection extends BaseInputConnection
return true;
}
@Override
public SurroundingText
getSurroundingText (int beforeLength, int afterLength,
int flags)
{
SurroundingText text;
/* Return if the input connection is out of date. */
if (view.icSerial < view.icGeneration)
return null;
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("getSurroundingText: " + beforeLength + ", "
+ afterLength));
text = EmacsNative.getSurroundingText (windowHandle, beforeLength,
afterLength, flags);
if (text != null)
Log.d (TAG, ("getSurroundingText: "
+ text.getSelectionStart ()
+ ","
+ text.getSelectionEnd ()
+ "+"
+ text.getOffset ()
+ ": "
+ text.getText ()));
return text;
}
/* Override functions which are not implemented. */

View file

@ -25,6 +25,7 @@
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.SurroundingText;
public final class EmacsNative
{
@ -222,6 +223,9 @@ public static native ExtractedText getExtractedText (short window,
public static native void requestSelectionUpdate (short window);
public static native void requestCursorUpdates (short window, int mode);
public static native void clearInputFlags (short window);
public static native SurroundingText getSurroundingText (short window,
int left, int right,
int flags);
/* Return the current value of the selection, or -1 upon

View file

@ -748,6 +748,7 @@ invocation of app_process (through android-emacs) can
window.view.setICMode (icMode);
icBeginSynchronous ();
window.view.icGeneration++;
window.view.imManager.restartInput (window.view);
icEndSynchronous ();
}

View file

@ -111,6 +111,13 @@ public final class EmacsView extends ViewGroup
details. */
private int icMode;
/* The number of calls to `resetIC' to have taken place the last
time an InputConnection was created. */
public long icSerial;
/* The number of calls to `recetIC' that have taken place. */
public volatile long icGeneration;
public
EmacsView (EmacsWindow window)
{
@ -627,6 +634,12 @@ else if (child.getVisibility () != GONE)
return null;
}
/* Set icSerial. If icSerial < icGeneration, the input connection
has been reset, and future input should be ignored until a new
connection is created. */
icSerial = icGeneration;
/* Reset flags set by the previous input method. */
EmacsNative.clearInputFlags (window.handle);

View file

@ -5636,6 +5636,149 @@ NATIVE_NAME (clearInputFlags) (JNIEnv *env, jobject object,
android_write_event (&event);
}
/* Context for a call to `getSurroundingText'. */
struct android_get_surrounding_text_context
{
/* Number of characters before the region to return. */
int before_length;
/* Number of characters after the region to return. */
int after_length;
/* The returned text, or NULL. */
char *text;
/* The size of that text in characters and bytes. */
ptrdiff_t length, bytes;
/* Offsets into that text. */
ptrdiff_t offset, start, end;
/* The window. */
android_window window;
};
/* Return the surrounding text in the surrounding text context
specified by DATA. */
static void
android_get_surrounding_text (void *data)
{
struct android_get_surrounding_text_context *request;
struct frame *f;
ptrdiff_t temp;
request = data;
/* Find the frame associated with the window. */
f = android_window_to_frame (NULL, request->window);
if (!f)
return;
/* Now get the surrounding text. */
request->text
= get_surrounding_text (f, request->before_length,
request->after_length, &request->length,
&request->bytes, &request->offset,
&request->start, &request->end);
/* Sort request->start and request->end for compatibility with some
bad input methods. */
if (request->end < request->start)
{
temp = request->start;
request->start = request->end;
request->end = temp;
}
}
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;
static jclass class;
static jmethodID constructor;
struct android_get_surrounding_text_context context;
jstring string;
jobject object;
/* Initialize CLASS if it has not yet been initialized. */
if (!class)
{
class
= (*env)->FindClass (env, ("android/view/inputmethod"
"/SurroundingText"));
#if __ANDROID_API__ < 31
/* If CLASS cannot be found, the version of Android currently
running is too old. */
if (!class)
{
(*env)->ExceptionClear (env);
return NULL;
}
#else /* __ANDROID_API__ >= 31 */
assert (class);
#endif /* __ANDROID_API__ < 31 */
class = (*env)->NewGlobalRef (env, class);
if (!class)
return NULL;
/* Now look for its constructor. */
constructor = (*env)->GetMethodID (env, class, "<init>",
"(Ljava/lang/CharSequence;III)V");
assert (constructor);
}
context.before_length = before_length;
context.after_length = after_length;
context.window = window;
context.text = NULL;
android_sync_edit ();
if (android_run_in_emacs_thread (android_get_surrounding_text,
&context))
return NULL;
if (!context.text)
return NULL;
/* Encode the returned text. */
string = android_text_to_string (env, context.text, context.length,
context.bytes);
free (context.text);
if (!string)
return NULL;
/* Create an SurroundingText object containing this information. */
object = (*env)->NewObject (env, class, constructor, string,
(jint) min (context.start,
TYPE_MAXIMUM (jint)),
(jint) min (context.end,
TYPE_MAXIMUM (jint)),
/* Adjust point offsets to fit into
Android's 0-based indexing. */
(jint) min (context.offset - 1,
TYPE_MAXIMUM (jint)));
if (!object)
return NULL;
return object;
}
#ifdef __clang__
#pragma clang diagnostic pop
#else

View file

@ -1732,6 +1732,106 @@ get_extracted_text (struct frame *f, ptrdiff_t n,
return buffer;
}
/* Return the text between the positions PT - LEFT and PT + RIGHT. If
the mark is active, return the range of text relative to the bounds
of the region instead.
Set *LENGTH to the number of characters returned, *BYTES to the
number of bytes returned, *OFFSET to the character position of the
returned text, and *START_RETURN and *END_RETURN to the mark and
point relative to that position. */
char *
get_surrounding_text (struct frame *f, ptrdiff_t left,
ptrdiff_t right, ptrdiff_t *length,
ptrdiff_t *bytes, ptrdiff_t *offset,
ptrdiff_t *start_return,
ptrdiff_t *end_return)
{
specpdl_ref count;
ptrdiff_t start, end, start_byte, end_byte, mark, temp;
char *buffer;
if (!WINDOW_LIVE_P (f->old_selected_window))
return NULL;
/* Save the excursion, as there will be extensive changes to the
selected window. */
count = SPECPDL_INDEX ();
record_unwind_protect_excursion ();
/* Inhibit quitting. */
specbind (Qinhibit_quit, Qt);
/* Temporarily switch to F's selected window at the time of the last
redisplay. */
select_window (f->old_selected_window, Qt);
buffer = NULL;
/* Figure out the bounds of the text to return. */
/* First, obtain start and end. */
end = get_mark ();
start = PT;
/* If the mark is not active, make it start and end. */
if (end == -1)
end = start;
/* Now sort start and end. */
if (end < start)
{
temp = start;
start = end;
end = temp;
}
/* And subtract left and right. */
if (INT_SUBTRACT_WRAPV (start, left, &start)
|| INT_ADD_WRAPV (end, right, &end))
goto finish;
start = max (start, BEGV);
end = min (end, ZV);
/* Detect overflow. */
if (!(start <= PT && PT <= end))
goto finish;
/* Convert the character positions to byte positions. */
start_byte = CHAR_TO_BYTE (start);
end_byte = CHAR_TO_BYTE (end);
/* Extract the text from the buffer. */
buffer = xmalloc (end_byte - start_byte);
copy_buffer (start, start_byte, end, end_byte,
buffer);
/* Get the mark. If it's not active, use PT. */
mark = get_mark ();
if (mark == -1)
mark = PT;
/* Return the offsets. Unlike `get_extracted_text', this need not
sort mark and point. */
*offset = start;
*start_return = mark - start;
*end_return = PT - start;
*length = end - start;
*bytes = end_byte - start_byte;
finish:
unbind_to (count, Qnil);
return buffer;
}
/* Return whether or not text conversion is temporarily disabled.
`reset' should always call this to determine whether or not to
disable the input method. */

View file

@ -143,6 +143,10 @@ extern void textconv_barrier (struct frame *, unsigned long);
extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *,
ptrdiff_t *, ptrdiff_t *, ptrdiff_t *,
ptrdiff_t *, bool *);
extern char *get_surrounding_text (struct frame *, ptrdiff_t,
ptrdiff_t, ptrdiff_t *,
ptrdiff_t *, ptrdiff_t *,
ptrdiff_t *, ptrdiff_t *);
extern bool conversion_disabled_p (void);
extern void register_textconv_interface (struct textconv_interface *);