aboutsummaryrefslogtreecommitdiffstats
path: root/java/org
diff options
context:
space:
mode:
authorPo Lu2023-02-15 12:23:03 +0800
committerPo Lu2023-02-15 12:23:03 +0800
commita158c1d5b964fda36f752998cef076d581dc4df4 (patch)
treeb9c7a22a7259dbd876be0e047304a338ef05e334 /java/org
parent5a7855e84abd56e55b456aef4a43ae7623e76899 (diff)
downloademacs-a158c1d5b964fda36f752998cef076d581dc4df4.tar.gz
emacs-a158c1d5b964fda36f752998cef076d581dc4df4.zip
Update Android port
* configure.ac (HAVE_TEXT_CONVERSION): Define on Android. * doc/emacs/input.texi (On-Screen Keyboards): Document ``text conversion'' slightly. * doc/lispref/commands.texi (Misc Events): Document new `text-conversion' event. * java/org/gnu/emacs/EmacsContextMenu.java (display): Use `syncRunnable'. * java/org/gnu/emacs/EmacsDialog.java (display): Likewise. * java/org/gnu/emacs/EmacsEditable.java: Delete file. * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): Reimplement from scratch. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Add new functions. * java/org/gnu/emacs/EmacsService.java (EmacsService, getEmacsView) (getLocationOnScreen, sync, getClipboardManager, restartEmacs): Use syncRunnable. (syncRunnable): New function. (updateIC, resetIC): New functions. * java/org/gnu/emacs/EmacsView.java (EmacsView): New field `inputConnection' and `icMode'. (onCreateInputConnection): Update accordingly. (setICMode, getICMode): New functions. * lisp/bindings.el (global-map): Ignore text conversion events. * src/alloc.c (mark_frame): Mark text conversion data. * src/android.c (struct android_emacs_service): New fields `update_ic' and `reset_ic'. (event_serial): Export. (android_query_sem): New function. (android_init_events): Initialize new semaphore. (android_write_event): Export. (android_select): Check for UI thread code. (setEmacsParams, android_init_emacs_service): Initialize new methods. (android_check_query, android_begin_query, android_end_query) (android_run_in_emacs_thread): (android_update_ic, android_reset_ic): New functions for managing synchronous queries from one thread to another. * src/android.h: Export new functions. * src/androidgui.h (enum android_event_type): Add input method events. (enum android_ime_operation, struct android_ime_event) (union android_event, enum android_ic_mode): New structs and enums. * src/androidterm.c (android_window_to_frame): Allow DPYINFO to be NULL. (android_decode_utf16, android_handle_ime_event) (handle_one_android_event, android_sync_edit) (android_copy_java_string, beginBatchEdit, endBatchEdit) (commitCompletion, deleteSurroundingText, finishComposingText) (getSelectedtext, getTextAfterCursor, getTextBeforeCursor) (setComposingText, setComposingRegion, setSelection, getSelection) (performEditorAction, getExtractedText): New functions. (struct android_conversion_query_context): (android_perform_conversion_query): (android_text_to_string): (struct android_get_selection_context): (android_get_selection): (struct android_get_extracted_text_context): (android_get_extracted_text): (struct android_extracted_text_request_class): (struct android_extracted_text_class): (android_update_selection): (android_reset_conversion): (android_set_point): (android_compose_region_changed): (android_notify_conversion): (text_conversion_interface): New functions and structures. (android_term_init): Initialize text conversion. * src/coding.c (syms_of_coding): Define Qutf_16le on Android. * src/frame.c (make_frame): Clear conversion data. (delete_frame): Reset conversion state. * src/frame.h (enum text_conversion_operation) (struct text_conversion_action, struct text_conversion_state) (GCALIGNED_STRUCT): Update structures. * src/keyboard.c (read_char, readable_events, kbd_buffer_get_event) (syms_of_keyboard): Handle text conversion events. * src/lisp.h: * src/process.c: Fix includes. * src/textconv.c (enum textconv_batch_edit_flags, textconv_query) (reset_frame_state, detect_conversion_events) (restore_selected_window, really_commit_text) (really_finish_composing_text, really_set_composing_text) (really_set_composing_region, really_delete_surrounding_text) (really_set_point, complete_edit) (handle_pending_conversion_events_1) (handle_pending_conversion_events, start_batch_edit) (end_batch_edit, commit_text, finish_composing_text) (set_composing_text, set_composing_region, textconv_set_point) (delete_surrounding_text, get_extracted_text) (report_selected_window_change, report_point_change) (register_texconv_interface): New functions. * src/textconv.h (struct textconv_interface) (TEXTCONV_SKIP_CONVERSION_REGION): Update prototype. * src/xdisp.c (mark_window_display_accurate_1): * src/xfns.c (xic_string_conversion_callback): * src/xterm.c (init_xterm): Adjust accordingly.
Diffstat (limited to 'java/org')
-rw-r--r--java/org/gnu/emacs/EmacsContextMenu.java15
-rw-r--r--java/org/gnu/emacs/EmacsDialog.java15
-rw-r--r--java/org/gnu/emacs/EmacsEditable.java300
-rw-r--r--java/org/gnu/emacs/EmacsInputConnection.java187
-rw-r--r--java/org/gnu/emacs/EmacsNative.java46
-rw-r--r--java/org/gnu/emacs/EmacsService.java117
-rw-r--r--java/org/gnu/emacs/EmacsView.java55
7 files changed, 267 insertions, 468 deletions
diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java
index 92429410d03..184c611bf9d 100644
--- a/java/org/gnu/emacs/EmacsContextMenu.java
+++ b/java/org/gnu/emacs/EmacsContextMenu.java
@@ -279,20 +279,7 @@ public class EmacsContextMenu
279 } 279 }
280 }; 280 };
281 281
282 synchronized (runnable) 282 EmacsService.syncRunnable (runnable);
283 {
284 EmacsService.SERVICE.runOnUiThread (runnable);
285
286 try
287 {
288 runnable.wait ();
289 }
290 catch (InterruptedException e)
291 {
292 EmacsNative.emacsAbort ();
293 }
294 }
295
296 return rc.thing; 283 return rc.thing;
297 } 284 }
298 285
diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java
index bd5e9ba8ee7..bc0333e99b9 100644
--- a/java/org/gnu/emacs/EmacsDialog.java
+++ b/java/org/gnu/emacs/EmacsDialog.java
@@ -317,20 +317,7 @@ public class EmacsDialog implements DialogInterface.OnDismissListener
317 } 317 }
318 }; 318 };
319 319
320 synchronized (runnable) 320 EmacsService.syncRunnable (runnable);
321 {
322 EmacsService.SERVICE.runOnUiThread (runnable);
323
324 try
325 {
326 runnable.wait ();
327 }
328 catch (InterruptedException e)
329 {
330 EmacsNative.emacsAbort ();
331 }
332 }
333
334 return rc.thing; 321 return rc.thing;
335 } 322 }
336 323
diff --git a/java/org/gnu/emacs/EmacsEditable.java b/java/org/gnu/emacs/EmacsEditable.java
deleted file mode 100644
index 79af65a6ccd..00000000000
--- a/java/org/gnu/emacs/EmacsEditable.java
+++ /dev/null
@@ -1,300 +0,0 @@
1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2
3Copyright (C) 2023 Free Software Foundation, Inc.
4
5This file is part of GNU Emacs.
6
7GNU Emacs is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or (at
10your option) any later version.
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19
20package org.gnu.emacs;
21
22import android.text.InputFilter;
23import android.text.SpannableStringBuilder;
24import android.text.Spanned;
25import android.text.SpanWatcher;
26import android.text.Selection;
27
28import android.content.Context;
29
30import android.view.inputmethod.InputMethodManager;
31import android.view.inputmethod.ExtractedText;
32import android.view.inputmethod.ExtractedTextRequest;
33
34import android.text.Spannable;
35
36import android.util.Log;
37
38import android.os.Build;
39
40/* Android input methods insist on having access to buffer contents.
41 Since Emacs is not designed like ``any other Android text editor'',
42 that is not possible.
43
44 This file provides a fake editing buffer that is designed to weasel
45 as much information as possible out of an input method, without
46 actually providing buffer contents to Emacs.
47
48 The basic idea is to have the fake editing buffer be initially
49 empty.
50
51 When the input method inserts composed text, it sets a flag.
52 Updates to the buffer while the flag is set are sent to Emacs to be
53 displayed as ``preedit text''.
54
55 Once some heuristics decide that composition has been completed,
56 the composed text is sent to Emacs, and the text that was inserted
57 in this editing buffer is erased. */
58
59public class EmacsEditable extends SpannableStringBuilder
60 implements SpanWatcher
61{
62 private static final String TAG = "EmacsEditable";
63
64 /* Whether or not composition is currently in progress. */
65 private boolean isComposing;
66
67 /* The associated input connection. */
68 private EmacsInputConnection connection;
69
70 /* The associated IM manager. */
71 private InputMethodManager imManager;
72
73 /* Any extracted text an input method may be monitoring. */
74 private ExtractedText extractedText;
75
76 /* The corresponding text request. */
77 private ExtractedTextRequest extractRequest;
78
79 /* The number of nested batch edits. */
80 private int batchEditCount;
81
82 /* Whether or not invalidateInput should be called upon batch edits
83 ending. */
84 private boolean pendingInvalidate;
85
86 /* The ``composing span'' indicating the bounds of an ongoing
87 character composition. */
88 private Object composingSpan;
89
90 public
91 EmacsEditable (EmacsInputConnection connection)
92 {
93 /* Initialize the editable with one initial space, so backspace
94 always works. */
95 super ();
96
97 Object tem;
98 Context context;
99
100 this.connection = connection;
101
102 context = connection.view.getContext ();
103 tem = context.getSystemService (Context.INPUT_METHOD_SERVICE);
104 imManager = (InputMethodManager) tem;
105
106 /* To watch for changes to text properties on Android, you
107 add... a text property. */
108 setSpan (this, 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
109 }
110
111 public void
112 endBatchEdit ()
113 {
114 if (batchEditCount < 1)
115 return;
116
117 if (--batchEditCount == 0 && pendingInvalidate)
118 invalidateInput ();
119 }
120
121 public void
122 beginBatchEdit ()
123 {
124 ++batchEditCount;
125 }
126
127 public void
128 setExtractedTextAndRequest (ExtractedText text,
129 ExtractedTextRequest request,
130 boolean monitor)
131 {
132 /* Extract the text. If monitor is set, also record it as the
133 text that is currently being extracted. */
134
135 text.startOffset = 0;
136 text.selectionStart = Selection.getSelectionStart (this);
137 text.selectionEnd = Selection.getSelectionStart (this);
138 text.text = this;
139
140 if (monitor)
141 {
142 extractedText = text;
143 extractRequest = request;
144 }
145 }
146
147 public void
148 compositionStart ()
149 {
150 isComposing = true;
151 }
152
153 public void
154 compositionEnd ()
155 {
156 isComposing = false;
157 sendComposingText (null);
158 }
159
160 private void
161 sendComposingText (String string)
162 {
163 EmacsWindow window;
164 long time, serial;
165
166 window = connection.view.window;
167
168 if (window.isDestroyed ())
169 return;
170
171 time = System.currentTimeMillis ();
172
173 /* A composition event is simply a special key event with a
174 keycode of -1. */
175
176 synchronized (window.eventStrings)
177 {
178 serial
179 = EmacsNative.sendKeyPress (window.handle, time, 0, -1, -1);
180
181 /* Save the string so that android_lookup_string can find
182 it. */
183 if (string != null)
184 window.saveUnicodeString ((int) serial, string);
185 }
186 }
187
188 private void
189 invalidateInput ()
190 {
191 int start, end, composingSpanStart, composingSpanEnd;
192
193 if (batchEditCount > 0)
194 {
195 Log.d (TAG, "invalidateInput: deferring for batch edit");
196 pendingInvalidate = true;
197 return;
198 }
199
200 pendingInvalidate = false;
201
202 start = Selection.getSelectionStart (this);
203 end = Selection.getSelectionEnd (this);
204
205 if (composingSpan != null)
206 {
207 composingSpanStart = getSpanStart (composingSpan);
208 composingSpanEnd = getSpanEnd (composingSpan);
209 }
210 else
211 {
212 composingSpanStart = -1;
213 composingSpanEnd = -1;
214 }
215
216 Log.d (TAG, "invalidateInput: now " + start + ", " + end);
217
218 /* Tell the input method that the cursor changed. */
219 imManager.updateSelection (connection.view, start, end,
220 composingSpanStart,
221 composingSpanEnd);
222
223 /* If there is any extracted text, tell the IME that it has
224 changed. */
225 if (extractedText != null)
226 imManager.updateExtractedText (connection.view,
227 extractRequest.token,
228 extractedText);
229 }
230
231 public SpannableStringBuilder
232 replace (int start, int end, CharSequence tb, int tbstart,
233 int tbend)
234 {
235 super.replace (start, end, tb, tbstart, tbend);
236
237 /* If a change happens during composition, perform the change and
238 then send the text being composed. */
239
240 if (isComposing)
241 sendComposingText (toString ());
242
243 return this;
244 }
245
246 private boolean
247 isSelectionSpan (Object span)
248 {
249 return ((Selection.SELECTION_START == span
250 || Selection.SELECTION_END == span)
251 && (getSpanFlags (span)
252 & Spanned.SPAN_INTERMEDIATE) == 0);
253 }
254
255 @Override
256 public void
257 onSpanAdded (Spannable text, Object what, int start, int end)
258 {
259 Log.d (TAG, "onSpanAdded: " + text + " " + what + " "
260 + start + " " + end);
261
262 /* Try to find the composing span. This isn't a public API. */
263
264 if (what.getClass ().getName ().contains ("ComposingText"))
265 composingSpan = what;
266
267 if (isSelectionSpan (what))
268 invalidateInput ();
269 }
270
271 @Override
272 public void
273 onSpanChanged (Spannable text, Object what, int ostart,
274 int oend, int nstart, int nend)
275 {
276 Log.d (TAG, "onSpanChanged: " + text + " " + what + " "
277 + nstart + " " + nend);
278
279 if (isSelectionSpan (what))
280 invalidateInput ();
281 }
282
283 @Override
284 public void
285 onSpanRemoved (Spannable text, Object what,
286 int start, int end)
287 {
288 Log.d (TAG, "onSpanRemoved: " + text + " " + what + " "
289 + start + " " + end);
290
291 if (isSelectionSpan (what))
292 invalidateInput ();
293 }
294
295 public boolean
296 isInBatchEdit ()
297 {
298 return batchEditCount > 0;
299 }
300}
diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java
index 897a393b984..3cf4419838b 100644
--- a/java/org/gnu/emacs/EmacsInputConnection.java
+++ b/java/org/gnu/emacs/EmacsInputConnection.java
@@ -25,6 +25,7 @@ import android.view.inputmethod.ExtractedText;
25import android.view.inputmethod.ExtractedTextRequest; 25import android.view.inputmethod.ExtractedTextRequest;
26import android.view.inputmethod.InputMethodManager; 26import android.view.inputmethod.InputMethodManager;
27import android.view.inputmethod.SurroundingText; 27import android.view.inputmethod.SurroundingText;
28import android.view.inputmethod.TextSnapshot;
28import android.view.KeyEvent; 29import android.view.KeyEvent;
29 30
30import android.text.Editable; 31import android.text.Editable;
@@ -38,138 +39,156 @@ import android.util.Log;
38public class EmacsInputConnection extends BaseInputConnection 39public class EmacsInputConnection extends BaseInputConnection
39{ 40{
40 private static final String TAG = "EmacsInputConnection"; 41 private static final String TAG = "EmacsInputConnection";
41 public EmacsView view; 42 private EmacsView view;
42 private EmacsEditable editable; 43 private short windowHandle;
43
44 /* The length of the last string to be committed. */
45 private int lastCommitLength;
46
47 int currentLargeOffset;
48 44
49 public 45 public
50 EmacsInputConnection (EmacsView view) 46 EmacsInputConnection (EmacsView view)
51 { 47 {
52 super (view, false); 48 super (view, true);
49
53 this.view = view; 50 this.view = view;
54 this.editable = new EmacsEditable (this); 51 this.windowHandle = view.window.handle;
55 } 52 }
56 53
57 @Override 54 @Override
58 public Editable 55 public boolean
59 getEditable () 56 beginBatchEdit ()
60 { 57 {
61 return editable; 58 Log.d (TAG, "beginBatchEdit");
59 EmacsNative.beginBatchEdit (windowHandle);
60 return true;
62 } 61 }
63 62
64 @Override 63 @Override
65 public boolean 64 public boolean
66 setComposingText (CharSequence text, int newCursorPosition) 65 endBatchEdit ()
67 { 66 {
68 editable.compositionStart (); 67 Log.d (TAG, "endBatchEdit");
69 super.setComposingText (text, newCursorPosition); 68 EmacsNative.endBatchEdit (windowHandle);
70 return true; 69 return true;
71 } 70 }
72 71
73 @Override 72 @Override
74 public boolean 73 public boolean
75 setComposingRegion (int start, int end) 74 commitCompletion (CompletionInfo info)
76 { 75 {
77 int i; 76 Log.d (TAG, "commitCompletion: " + info);
78 77 EmacsNative.commitCompletion (windowHandle,
79 if (lastCommitLength != 0) 78 info.getText ().toString (),
80 { 79 info.getPosition ());
81 Log.d (TAG, "Restarting composition for: " + lastCommitLength); 80 return true;
82 81 }
83 for (i = 0; i < lastCommitLength; ++i)
84 sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
85 KeyEvent.KEYCODE_DEL));
86 82
87 lastCommitLength = 0; 83 @Override
88 } 84 public boolean
85 commitText (CharSequence text, int newCursorPosition)
86 {
87 Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
88 EmacsNative.commitText (windowHandle, text.toString (),
89 newCursorPosition);
90 return true;
91 }
89 92
90 editable.compositionStart (); 93 @Override
91 super.setComposingRegion (start, end); 94 public boolean
95 deleteSurroundingText (int leftLength, int rightLength)
96 {
97 Log.d (TAG, ("deleteSurroundingText: "
98 + leftLength + " " + rightLength));
99 EmacsNative.deleteSurroundingText (windowHandle, leftLength,
100 rightLength);
92 return true; 101 return true;
93 } 102 }
94 103
95 @Override 104 @Override
96 public boolean 105 public boolean
97 finishComposingText () 106 finishComposingText ()
98 { 107 {
99 editable.compositionEnd (); 108 Log.d (TAG, "finishComposingText");
100 return super.finishComposingText (); 109
110 EmacsNative.finishComposingText (windowHandle);
111 return true;
112 }
113
114 @Override
115 public String
116 getSelectedText (int flags)
117 {
118 Log.d (TAG, "getSelectedText: " + flags);
119
120 return EmacsNative.getSelectedText (windowHandle, flags);
121 }
122
123 @Override
124 public String
125 getTextAfterCursor (int length, int flags)
126 {
127 Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
128
129 return EmacsNative.getTextAfterCursor (windowHandle, length,
130 flags);
131 }
132
133 @Override
134 public String
135 getTextBeforeCursor (int length, int flags)
136 {
137 Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
138
139 return EmacsNative.getTextBeforeCursor (windowHandle, length,
140 flags);
101 } 141 }
102 142
103 @Override 143 @Override
104 public boolean 144 public boolean
105 beginBatchEdit () 145 setComposingText (CharSequence text, int newCursorPosition)
106 { 146 {
107 editable.beginBatchEdit (); 147 Log.d (TAG, "setComposingText: " + newCursorPosition);
108 return super.beginBatchEdit (); 148
149 EmacsNative.setComposingText (windowHandle, text.toString (),
150 newCursorPosition);
151 return true;
109 } 152 }
110 153
111 @Override 154 @Override
112 public boolean 155 public boolean
113 endBatchEdit () 156 setComposingRegion (int start, int end)
114 { 157 {
115 editable.endBatchEdit (); 158 Log.d (TAG, "setComposingRegion: " + start + " " + end);
116 return super.endBatchEdit (); 159
160 EmacsNative.setComposingRegion (windowHandle, start, end);
161 return true;
117 } 162 }
118 163
119 @Override 164 @Override
120 public boolean 165 public boolean
121 commitText (CharSequence text, int newCursorPosition) 166 performEditorAction (int editorAction)
122 { 167 {
123 editable.compositionEnd (); 168 Log.d (TAG, "performEditorAction: " + editorAction);
124 super.commitText (text, newCursorPosition);
125
126 /* An observation is that input methods rarely recompose trailing
127 spaces. Avoid re-setting the commit length in that case. */
128
129 if (text.toString ().equals (" "))
130 lastCommitLength += 1;
131 else
132 /* At this point, the editable is now empty.
133
134 The input method may try to cancel the edit upon a subsequent
135 backspace by calling setComposingRegion with a region that is
136 the length of TEXT.
137
138 Record this length in order to be able to send backspace
139 events to ``delete'' the text in that case. */
140 lastCommitLength = text.length ();
141
142 Log.d (TAG, "commitText: \"" + text + "\"");
143 169
170 EmacsNative.performEditorAction (windowHandle, editorAction);
144 return true; 171 return true;
145 } 172 }
146 173
147 /* Return a large offset, cycling through 100000, 30000, 0. 174 @Override
148 The offset is typically used to force the input method to update 175 public ExtractedText
149 its notion of ``surrounding text'', bypassing any caching that 176 getExtractedText (ExtractedTextRequest request, int flags)
150 it might have in progress. 177 {
178 Log.d (TAG, "getExtractedText: " + request + " " + flags);
179
180 return EmacsNative.getExtractedText (windowHandle, request,
181 flags);
182 }
151 183
152 There must be another way to do this, but I can't find it. */ 184
185 /* Override functions which are not implemented. */
153 186
154 public int 187 @Override
155 largeSelectionOffset () 188 public TextSnapshot
189 takeSnapshot ()
156 { 190 {
157 switch (currentLargeOffset) 191 Log.d (TAG, "takeSnapshot");
158 { 192 return null;
159 case 0:
160 currentLargeOffset = 100000;
161 return 100000;
162
163 case 100000:
164 currentLargeOffset = 30000;
165 return 30000;
166
167 case 30000:
168 currentLargeOffset = 0;
169 return 0;
170 }
171
172 currentLargeOffset = 0;
173 return -1;
174 } 193 }
175} 194}
diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java
index f0219843d35..aaac9446510 100644
--- a/java/org/gnu/emacs/EmacsNative.java
+++ b/java/org/gnu/emacs/EmacsNative.java
@@ -22,6 +22,8 @@ package org.gnu.emacs;
22import java.lang.System; 22import java.lang.System;
23 23
24import android.content.res.AssetManager; 24import android.content.res.AssetManager;
25import android.view.inputmethod.ExtractedText;
26import android.view.inputmethod.ExtractedTextRequest;
25 27
26public class EmacsNative 28public class EmacsNative
27{ 29{
@@ -161,6 +163,50 @@ public class EmacsNative
161 descriptor, or NULL if there is none. */ 163 descriptor, or NULL if there is none. */
162 public static native byte[] getProcName (int fd); 164 public static native byte[] getProcName (int fd);
163 165
166 /* Notice that the Emacs thread will now start waiting for the main
167 thread's looper to respond. */
168 public static native void beginSynchronous ();
169
170 /* Notice that the Emacs thread will has finished waiting for the
171 main thread's looper to respond. */
172 public static native void endSynchronous ();
173
174
175
176 /* Input connection functions. These mostly correspond to their
177 counterparts in Android's InputConnection. */
178
179 public static native void beginBatchEdit (short window);
180 public static native void endBatchEdit (short window);
181 public static native void commitCompletion (short window, String text,
182 int position);
183 public static native void commitText (short window, String text,
184 int position);
185 public static native void deleteSurroundingText (short window,
186 int leftLength,
187 int rightLength);
188 public static native void finishComposingText (short window);
189 public static native String getSelectedText (short window, int flags);
190 public static native String getTextAfterCursor (short window, int length,
191 int flags);
192 public static native String getTextBeforeCursor (short window, int length,
193 int flags);
194 public static native void setComposingText (short window, String text,
195 int newCursorPosition);
196 public static native void setComposingRegion (short window, int start,
197 int end);
198 public static native void setSelection (short window, int start, int end);
199 public static native void performEditorAction (short window,
200 int editorAction);
201 public static native ExtractedText getExtractedText (short window,
202 ExtractedTextRequest req,
203 int flags);
204
205
206 /* Return the current value of the selection, or -1 upon
207 failure. */
208 public static native int getSelection (short window);
209
164 static 210 static
165 { 211 {
166 /* Older versions of Android cannot link correctly with shared 212 /* Older versions of Android cannot link correctly with shared
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
index 2ec2ddf9bda..855a738a30f 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -80,6 +80,11 @@ public class EmacsService extends Service
80 private EmacsThread thread; 80 private EmacsThread thread;
81 private Handler handler; 81 private Handler handler;
82 82
83 /* Keep this in synch with androidgui.h. */
84 public static final int IC_MODE_NULL = 0;
85 public static final int IC_MODE_ACTION = 1;
86 public static final int IC_MODE_TEXT = 2;
87
83 /* Display metrics used by font backends. */ 88 /* Display metrics used by font backends. */
84 public DisplayMetrics metrics; 89 public DisplayMetrics metrics;
85 90
@@ -258,20 +263,7 @@ public class EmacsService extends Service
258 } 263 }
259 }; 264 };
260 265
261 synchronized (runnable) 266 syncRunnable (runnable);
262 {
263 runOnUiThread (runnable);
264
265 try
266 {
267 runnable.wait ();
268 }
269 catch (InterruptedException e)
270 {
271 EmacsNative.emacsAbort ();
272 }
273 }
274
275 return view.thing; 267 return view.thing;
276 } 268 }
277 269
@@ -292,19 +284,7 @@ public class EmacsService extends Service
292 } 284 }
293 }; 285 };
294 286
295 synchronized (runnable) 287 syncRunnable (runnable);
296 {
297 runOnUiThread (runnable);
298
299 try
300 {
301 runnable.wait ();
302 }
303 catch (InterruptedException e)
304 {
305 EmacsNative.emacsAbort ();
306 }
307 }
308 } 288 }
309 289
310 public void 290 public void
@@ -502,19 +482,7 @@ public class EmacsService extends Service
502 } 482 }
503 }; 483 };
504 484
505 synchronized (runnable) 485 syncRunnable (runnable);
506 {
507 runOnUiThread (runnable);
508
509 try
510 {
511 runnable.wait ();
512 }
513 catch (InterruptedException e)
514 {
515 EmacsNative.emacsAbort ();
516 }
517 }
518 } 486 }
519 487
520 488
@@ -594,20 +562,7 @@ public class EmacsService extends Service
594 } 562 }
595 }; 563 };
596 564
597 synchronized (runnable) 565 syncRunnable (runnable);
598 {
599 runOnUiThread (runnable);
600
601 try
602 {
603 runnable.wait ();
604 }
605 catch (InterruptedException e)
606 {
607 EmacsNative.emacsAbort ();
608 }
609 }
610
611 return manager.thing; 566 return manager.thing;
612 } 567 }
613 568
@@ -622,4 +577,58 @@ public class EmacsService extends Service
622 startActivity (intent); 577 startActivity (intent);
623 System.exit (0); 578 System.exit (0);
624 } 579 }
580
581 /* Wait synchronously for the specified RUNNABLE to complete in the
582 UI thread. Must be called from the Emacs thread. */
583
584 public static void
585 syncRunnable (Runnable runnable)
586 {
587 EmacsNative.beginSynchronous ();
588
589 synchronized (runnable)
590 {
591 SERVICE.runOnUiThread (runnable);
592
593 while (true)
594 {
595 try
596 {
597 runnable.wait ();
598 break;
599 }
600 catch (InterruptedException e)
601 {
602 continue;
603 }
604 }
605 }
606
607 EmacsNative.endSynchronous ();
608 }
609
610 public void
611 updateIC (EmacsWindow window, int newSelectionStart,
612 int newSelectionEnd, int composingRegionStart,
613 int composingRegionEnd)
614 {
615 Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
616 + " " + newSelectionEnd + " "
617 + composingRegionStart + " "
618 + composingRegionEnd));
619 window.view.imManager.updateSelection (window.view,
620 newSelectionStart,
621 newSelectionEnd,
622 composingRegionStart,
623 composingRegionEnd);
624 }
625
626 public void
627 resetIC (EmacsWindow window, int icMode)
628 {
629 Log.d (TAG, "resetIC: " + window);
630
631 window.view.setICMode (icMode);
632 window.view.imManager.restartInput (window.view);
633 }
625}; 634};
diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java
index bc3716f6da8..52da6d41f7d 100644
--- a/java/org/gnu/emacs/EmacsView.java
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -103,6 +103,13 @@ public class EmacsView extends ViewGroup
103 displayed whenever possible. */ 103 displayed whenever possible. */
104 public boolean isCurrentlyTextEditor; 104 public boolean isCurrentlyTextEditor;
105 105
106 /* The associated input connection. */
107 private EmacsInputConnection inputConnection;
108
109 /* The current IC mode. See `android_reset_ic' for more
110 details. */
111 private int icMode;
112
106 public 113 public
107 EmacsView (EmacsWindow window) 114 EmacsView (EmacsWindow window)
108 { 115 {
@@ -554,14 +561,46 @@ public class EmacsView extends ViewGroup
554 public InputConnection 561 public InputConnection
555 onCreateInputConnection (EditorInfo info) 562 onCreateInputConnection (EditorInfo info)
556 { 563 {
564 int selection, mode;
565
566 /* Figure out what kind of IME behavior Emacs wants. */
567 mode = getICMode ();
568
557 /* Make sure the input method never displays a full screen input 569 /* Make sure the input method never displays a full screen input
558 box that obscures Emacs. */ 570 box that obscures Emacs. */
559 info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; 571 info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
560 572
561 /* Set a reasonable inputType. */ 573 /* Set a reasonable inputType. */
562 info.inputType = InputType.TYPE_NULL; 574 info.inputType = InputType.TYPE_CLASS_TEXT;
575
576 /* Obtain the current position of point and set it as the
577 selection. */
578 selection = EmacsNative.getSelection (window.handle);
579
580 Log.d (TAG, "onCreateInputConnection: current selection is: " + selection);
581
582 /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't
583 initialize the input connection. */
584 if (selection == -1 || mode == EmacsService.IC_MODE_NULL)
585 {
586 info.inputType = InputType.TYPE_NULL;
587 return null;
588 }
589
590 if (mode == EmacsService.IC_MODE_ACTION)
591 info.imeOptions |= EditorInfo.IME_ACTION_DONE;
563 592
564 return null; 593 /* Set the initial selection fields. */
594 info.initialSelStart = selection;
595 info.initialSelEnd = selection;
596
597 /* Create the input connection if necessary. */
598
599 if (inputConnection == null)
600 inputConnection = new EmacsInputConnection (this);
601
602 /* Return the input connection. */
603 return inputConnection;
565 } 604 }
566 605
567 @Override 606 @Override
@@ -572,4 +611,16 @@ public class EmacsView extends ViewGroup
572 keyboard. */ 611 keyboard. */
573 return isCurrentlyTextEditor; 612 return isCurrentlyTextEditor;
574 } 613 }
614
615 public synchronized void
616 setICMode (int icMode)
617 {
618 this.icMode = icMode;
619 }
620
621 public synchronized int
622 getICMode ()
623 {
624 return icMode;
625 }
575}; 626};