OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.content.browser.input; | |
6 | |
7 import android.os.Bundle; | |
8 import android.view.KeyEvent; | |
9 import android.view.inputmethod.CompletionInfo; | |
10 import android.view.inputmethod.CorrectionInfo; | |
11 import android.view.inputmethod.EditorInfo; | |
12 import android.view.inputmethod.ExtractedText; | |
13 import android.view.inputmethod.ExtractedTextRequest; | |
14 import android.view.inputmethod.InputConnection; | |
15 | |
16 import org.chromium.base.Log; | |
17 import org.chromium.base.ThreadUtils; | |
18 | |
19 import java.util.concurrent.BlockingQueue; | |
20 import java.util.concurrent.LinkedBlockingQueue; | |
21 | |
22 /** | |
23 * Chromium's implementation of {@link InputConnection} to communicate with exte rnal input method | |
24 * apps. | |
25 */ | |
26 public class ChromiumInputConnection implements ChromiumBaseInputConnection { | |
aelias_OOO_until_Jul13
2016/01/27 20:55:57
Also, how about renaming this to ThreadedInputConn
Changwan Ryu
2016/01/28 07:38:47
Done.
| |
27 private static final String TAG = "cr_Ime"; | |
28 | |
29 private final ImeAdapter mImeAdapter; | |
30 private final ThreadManager mThreadManager; | |
31 | |
32 private int mNumNestedBatchEdits = 0; | |
33 | |
34 private final BlockingQueue<TextInputState> mQueue = new LinkedBlockingQueue <>(); | |
35 // Only accessible on UI thread. | |
36 private TextInputState mLastUpdatedTextInputState; | |
37 | |
38 ChromiumInputConnection(ImeAdapter imeAdapter, ThreadManager threadManager) { | |
39 Log.d(TAG, "constructor"); | |
40 ImeUtils.assertOnUiThread(); | |
41 mImeAdapter = imeAdapter; | |
42 mThreadManager = threadManager; | |
43 } | |
44 | |
45 void initializeOutAttrsOnUiThread(int inputType, int inputFlags, EditorInfo outAttrs) { | |
46 Log.d(TAG, "initializeOutAttrs"); | |
47 ImeUtils.assertOnUiThread(); | |
48 int initialSelStart = 0; | |
49 int initialSelEnd = 0; | |
50 if (mLastUpdatedTextInputState != null) { | |
51 initialSelStart = mLastUpdatedTextInputState.selection().start(); | |
52 initialSelEnd = mLastUpdatedTextInputState.selection().end(); | |
53 } | |
54 ImeUtils.computeEditorInfo(inputType, inputFlags, initialSelStart, initi alSelEnd, outAttrs); | |
55 } | |
56 | |
57 @Override | |
58 public void updateStateOnUiThread(final String text, final int selectionStar t, | |
59 final int selectionEnd, final int compositionStart, final int compos itionEnd, | |
60 boolean singleLine, final boolean isNonImeChange) { | |
61 ImeUtils.assertOnUiThread(); | |
62 | |
63 final TextInputState newState = | |
64 new TextInputState(text, new Range(selectionStart, selectionEnd) , | |
65 new Range(compositionStart, compositionEnd), singleLine, !isNonImeChange); | |
66 Log.d(TAG, "updateState: %s", newState); | |
67 mLastUpdatedTextInputState = newState; | |
68 | |
69 addToQueueOnUiThread(newState); | |
70 if (isNonImeChange) { | |
71 mThreadManager.post(new Runnable() { | |
72 @Override | |
73 public void run() { | |
74 checkQueueForNonImeUpdate(); | |
75 } | |
76 }); | |
77 } | |
78 } | |
79 | |
80 /** | |
81 * @see ChromiumBaseInputConnection#getThreadManager() | |
82 */ | |
83 @Override | |
84 public ThreadManager getThreadManager() { | |
85 return mThreadManager; | |
86 } | |
87 | |
88 /** | |
89 * @see ChromiumBaseInputConnection#onRestartInputOnUiThread() | |
90 */ | |
91 @Override | |
92 public void onRestartInputOnUiThread() {} | |
93 | |
94 /** | |
95 * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent) | |
96 */ | |
97 @Override | |
98 public boolean sendKeyEventOnUiThread(final KeyEvent event) { | |
99 ImeUtils.assertOnUiThread(); | |
100 mThreadManager.post(new Runnable() { | |
101 @Override | |
102 public void run() { | |
103 sendKeyEvent(event); | |
104 } | |
105 }); | |
106 return true; | |
107 } | |
108 | |
109 /** | |
110 * @see ChromiumBaseInputConnection#moveCursorToSelectionEndOnUiThread() | |
111 */ | |
112 @Override | |
113 public void moveCursorToSelectionEndOnUiThread() { | |
114 mThreadManager.post(new Runnable() { | |
115 @Override | |
116 public void run() { | |
117 TextInputState textInputState = requestTextInputState(); | |
118 if (textInputState == null) return; | |
119 Range selection = textInputState.selection(); | |
120 setSelection(selection.end(), selection.end()); | |
121 } | |
122 }); | |
123 } | |
124 | |
125 // TODO(changwan): unblock whenever needed, e.g. renderer gone or swapped ou t. | |
126 @Override | |
127 public void unblock() { | |
128 Log.d(TAG, "unblock"); | |
129 ImeUtils.assertOnUiThread(); | |
130 addToQueueOnUiThread(new TextInputState.Unblocker()); | |
131 mThreadManager.post(new Runnable() { | |
132 @Override | |
133 public void run() { | |
134 checkQueueForUnblocker(); | |
135 } | |
136 }); | |
137 } | |
138 | |
139 private void checkQueueForNonImeUpdate() { | |
140 assertOnImeThread(); | |
141 TextInputState state = mQueue.peek(); | |
142 if (state == null) { | |
143 Log.d(TAG, "checkQueueForNonImeUpdate: no state"); | |
144 return; | |
145 } | |
146 mQueue.poll(); | |
147 ImeUtils.assertReally(!state.fromIme()); | |
148 updateSelection(state); | |
149 Log.d(TAG, "checkQueueForNonImeUpdate done: %d", mQueue.size()); | |
150 } | |
151 | |
152 private void checkQueueForUnblocker() { | |
153 Log.d(TAG, "checkQueueForUnblocker"); | |
154 assertOnImeThread(); | |
155 while (true) { | |
156 TextInputState state = mQueue.peek(); | |
157 if (state == null) return; | |
158 // Clear all the states in the queue. | |
159 mQueue.poll(); | |
160 if (state.shouldUnblock()) return; | |
161 } | |
162 } | |
163 | |
164 private void updateSelection(TextInputState textInputState) { | |
165 assertOnImeThread(); | |
166 if (mNumNestedBatchEdits != 0) return; | |
167 ImeUtils.assertReally(textInputState != null); | |
168 Range selection = textInputState.selection(); | |
169 Range composition = textInputState.composition(); | |
170 mImeAdapter.updateSelection( | |
171 selection.start(), selection.end(), composition.start(), composi tion.end()); | |
172 } | |
173 | |
174 private TextInputState requestTextInputState() { | |
175 Log.d(TAG, "requestTextInputState"); | |
176 ThreadUtils.postOnUiThread(new Runnable() { | |
177 @Override | |
178 public void run() { | |
179 boolean result = mImeAdapter.requestTextInputStateUpdate(); | |
180 if (!result) unblock(); | |
181 } | |
182 }); | |
183 return blockAndGetStateUpdate(); | |
184 } | |
185 | |
186 private void addToQueueOnUiThread(TextInputState textInputState) { | |
187 ImeUtils.assertOnUiThread(); | |
188 try { | |
189 mQueue.put(textInputState); | |
190 } catch (InterruptedException e) { | |
191 Log.e(TAG, "addToQueueOnUiThread interrupted", e); | |
192 } | |
193 Log.d(TAG, "addToQueueOnUiThread finished: %d", mQueue.size()); | |
194 } | |
195 | |
196 /** | |
197 * @return BlockingQueue for white box unit testing. | |
198 */ | |
199 BlockingQueue<TextInputState> getQueueForTest() { | |
200 return mQueue; | |
201 } | |
202 | |
203 private void assertOnImeThread() { | |
204 ImeUtils.assertReally(mThreadManager.runningOnThisThread()); | |
205 } | |
206 | |
207 /** | |
208 * Block until we get the expected state update. | |
209 * @return TextInputState if we get it successfully. null otherwise. | |
210 */ | |
211 private TextInputState blockAndGetStateUpdate() { | |
212 Log.d(TAG, "blockAndGetStateUpdate"); | |
213 assertOnImeThread(); | |
214 while (true) { | |
215 TextInputState state; | |
216 try { | |
217 state = mQueue.take(); | |
218 } catch (InterruptedException e) { | |
219 e.printStackTrace(); | |
220 return null; | |
221 } | |
222 if (state == null) return null; | |
223 if (state.fromIme()) { | |
224 if (state.shouldUnblock()) { | |
225 Log.d(TAG, "blockAndGetStateUpdate: unblocked"); | |
226 return null; | |
227 } | |
228 updateSelection(state); | |
229 Log.d(TAG, "blockAndGetStateUpdate done: %d", mQueue.size()); | |
230 return state; | |
231 } | |
232 // Ignore when state is not from IME. | |
233 } | |
234 } | |
235 | |
236 /** | |
237 * @see InputConnection#setComposingText(java.lang.CharSequence, int) | |
238 */ | |
239 @Override | |
240 public boolean setComposingText(final CharSequence text, final int newCursor Position) { | |
241 Log.d(TAG, "setComposingText [%s] [%d]", text, newCursorPosition); | |
242 assertOnImeThread(); | |
243 ThreadUtils.postOnUiThread(new Runnable() { | |
244 @Override | |
245 public void run() { | |
246 mImeAdapter.sendCompositionToNative(text, newCursorPosition, fal se); | |
247 } | |
248 }); | |
249 return true; | |
250 } | |
251 | |
252 /** | |
253 * @see InputConnection#commitText(java.lang.CharSequence, int) | |
254 */ | |
255 @Override | |
256 public boolean commitText(final CharSequence text, final int newCursorPositi on) { | |
aelias_OOO_until_Jul13
2016/01/27 20:46:06
I just noticed that BaseInputConnection.java calls
Changwan Ryu
2016/01/28 07:38:47
You're right. Added for M and below. I think it sp
| |
257 Log.d(TAG, "commitText [%s] [%d]", text, newCursorPosition); | |
258 assertOnImeThread(); | |
259 ThreadUtils.postOnUiThread(new Runnable() { | |
260 @Override | |
261 public void run() { | |
262 mImeAdapter.sendCompositionToNative(text, newCursorPosition, tex t.length() > 0); | |
263 } | |
264 }); | |
265 return true; | |
266 } | |
267 | |
268 /** | |
269 * @see InputConnection#performEditorAction(int) | |
270 */ | |
271 @Override | |
272 public boolean performEditorAction(final int actionCode) { | |
273 Log.d(TAG, "performEditorAction [%d]", actionCode); | |
274 assertOnImeThread(); | |
275 ThreadUtils.postOnUiThread(new Runnable() { | |
276 @Override | |
277 public void run() { | |
278 mImeAdapter.performEditorAction(actionCode); | |
279 } | |
280 }); | |
281 return true; | |
282 } | |
283 | |
284 /** | |
285 * @see InputConnection#performContextMenuAction(int) | |
286 */ | |
287 @Override | |
288 public boolean performContextMenuAction(final int id) { | |
289 Log.d(TAG, "performContextMenuAction [%d]", id); | |
290 assertOnImeThread(); | |
291 ThreadUtils.postOnUiThread(new Runnable() { | |
292 @Override | |
293 public void run() { | |
294 mImeAdapter.performContextMenuAction(id); | |
295 } | |
296 }); | |
297 return true; | |
298 } | |
299 | |
300 /** | |
301 * @see InputConnection#getExtractedText(android.view.inputmethod.ExtractedT extRequest, | |
302 * int) | |
303 */ | |
304 @Override | |
305 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag s) { | |
306 Log.d(TAG, "getExtractedText"); | |
307 assertOnImeThread(); | |
308 TextInputState textInputState = requestTextInputState(); | |
309 if (textInputState == null) return null; | |
310 ExtractedText extractedText = new ExtractedText(); | |
311 extractedText.text = textInputState.text(); | |
312 extractedText.partialEndOffset = textInputState.text().length(); | |
313 extractedText.selectionStart = textInputState.selection().start(); | |
314 extractedText.selectionEnd = textInputState.selection().end(); | |
315 extractedText.flags = textInputState.singleLine() ? ExtractedText.FLAG_S INGLE_LINE : 0; | |
316 return extractedText; | |
317 } | |
318 | |
319 /** | |
320 * @see InputConnection#beginBatchEdit() | |
321 */ | |
322 @Override | |
323 public boolean beginBatchEdit() { | |
324 Log.d(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0)); | |
325 assertOnImeThread(); | |
326 mNumNestedBatchEdits++; | |
327 return true; | |
328 } | |
329 | |
330 /** | |
331 * @see InputConnection#endBatchEdit() | |
332 */ | |
333 @Override | |
334 public boolean endBatchEdit() { | |
335 assertOnImeThread(); | |
336 if (mNumNestedBatchEdits == 0) return false; | |
337 --mNumNestedBatchEdits; | |
338 Log.d(TAG, "endBatchEdit [%b]", (mNumNestedBatchEdits == 0)); | |
339 if (mNumNestedBatchEdits == 0) { | |
340 updateSelection(requestTextInputState()); | |
341 } | |
342 return mNumNestedBatchEdits != 0; | |
343 } | |
344 | |
345 /** | |
346 * @see InputConnection#deleteSurroundingText(int, int) | |
347 */ | |
348 @Override | |
349 public boolean deleteSurroundingText(final int beforeLength, final int after Length) { | |
350 Log.d(TAG, "deleteSurroundingText [%d %d]", beforeLength, afterLength); | |
351 assertOnImeThread(); | |
352 ThreadUtils.postOnUiThread(new Runnable() { | |
353 @Override | |
354 public void run() { | |
355 mImeAdapter.deleteSurroundingText(beforeLength, afterLength); | |
356 } | |
357 }); | |
358 return true; | |
359 } | |
360 | |
361 /** | |
362 * @see InputConnection#sendKeyEvent(android.view.KeyEvent) | |
363 */ | |
364 @Override | |
365 public boolean sendKeyEvent(final KeyEvent event) { | |
aelias_OOO_until_Jul13
2016/01/27 20:46:06
For pending accent, I guess we can take the same a
Changwan Ryu
2016/01/28 07:38:47
Done.
| |
366 Log.d(TAG, "sendKeyEvent [%d %d]", event.getAction(), event.getKeyCode() ); | |
367 assertOnImeThread(); | |
368 ThreadUtils.postOnUiThread(new Runnable() { | |
369 @Override | |
370 public void run() { | |
371 mImeAdapter.sendKeyEvent(event); | |
372 } | |
373 }); | |
374 return true; | |
375 } | |
376 | |
377 /** | |
378 * @see InputConnection#finishComposingText() | |
379 */ | |
380 @Override | |
381 public boolean finishComposingText() { | |
382 Log.d(TAG, "finishComposingText"); | |
383 // This is the only function that may be called on UI thread because | |
384 // of direct calls from InputMethodManager. | |
385 ThreadUtils.postOnUiThread(new Runnable() { | |
386 @Override | |
387 public void run() { | |
388 mImeAdapter.finishComposingText(); | |
389 } | |
390 }); | |
391 return true; | |
392 } | |
393 | |
394 /** | |
395 * @see InputConnection#setSelection(int, int) | |
396 */ | |
397 @Override | |
398 public boolean setSelection(final int start, final int end) { | |
399 Log.d(TAG, "setSelection [%d %d]", start, end); | |
400 assertOnImeThread(); | |
401 ThreadUtils.postOnUiThread(new Runnable() { | |
402 @Override | |
403 public void run() { | |
404 mImeAdapter.setEditableSelectionOffsets(start, end); | |
405 } | |
406 }); | |
407 return true; | |
408 } | |
409 | |
410 /** | |
411 * @see InputConnection#setComposingRegion(int, int) | |
412 */ | |
413 @Override | |
414 public boolean setComposingRegion(final int start, final int end) { | |
415 Log.d(TAG, "setComposingRegion [%d %d]", start, end); | |
416 assertOnImeThread(); | |
417 ThreadUtils.postOnUiThread(new Runnable() { | |
418 @Override | |
419 public void run() { | |
420 mImeAdapter.setComposingRegion(start, end); | |
421 } | |
422 }); | |
423 return true; | |
424 } | |
425 | |
426 /** | |
427 * @see InputConnection#getTextBeforeCursor(int, int) | |
428 */ | |
429 @Override | |
430 public CharSequence getTextBeforeCursor(int maxChars, int flags) { | |
431 Log.d(TAG, "getTextBeforeCursor [%d %x]", maxChars, flags); | |
432 assertOnImeThread(); | |
433 TextInputState textInputState = requestTextInputState(); | |
434 if (textInputState == null) return null; | |
435 return textInputState.getTextBeforeSelection(maxChars); | |
436 } | |
437 | |
438 /** | |
439 * @see InputConnection#getTextAfterCursor(int, int) | |
440 */ | |
441 @Override | |
442 public CharSequence getTextAfterCursor(int maxChars, int flags) { | |
443 Log.d(TAG, "getTextAfterCursor [%d %x]", maxChars, flags); | |
444 assertOnImeThread(); | |
445 TextInputState textInputState = requestTextInputState(); | |
446 if (textInputState == null) return null; | |
447 return textInputState.getTextAfterSelection(maxChars); | |
448 } | |
449 | |
450 /** | |
451 * @see InputConnection#getSelectedText(int) | |
452 */ | |
453 @Override | |
454 public CharSequence getSelectedText(int flags) { | |
455 Log.d(TAG, "getSelectedText [%x]", flags); | |
456 assertOnImeThread(); | |
457 TextInputState textInputState = requestTextInputState(); | |
458 if (textInputState == null) return null; | |
459 return textInputState.getSelectedText(); | |
460 } | |
461 | |
462 /** | |
463 * @see InputConnection#getCursorCapsMode(int) | |
464 */ | |
465 @Override | |
466 public int getCursorCapsMode(int reqModes) { | |
467 Log.d(TAG, "getCursorCapsMode [%x]", reqModes); | |
468 assertOnImeThread(); | |
469 // TODO(changwan): Auto-generated method stub | |
470 return 0; | |
471 } | |
472 | |
473 /** | |
474 * @see InputConnection#commitCompletion(android.view.inputmethod.Completion Info) | |
475 */ | |
476 @Override | |
477 public boolean commitCompletion(CompletionInfo text) { | |
478 Log.d(TAG, "commitCompletion [%s]", text); | |
479 assertOnImeThread(); | |
480 // TODO(changwan): Auto-generated method stub | |
481 return false; | |
482 } | |
483 | |
484 /** | |
485 * @see InputConnection#commitCorrection(android.view.inputmethod.Correction Info) | |
486 */ | |
487 @Override | |
488 public boolean commitCorrection(CorrectionInfo correctionInfo) { | |
489 Log.d(TAG, "commitCorrection [%s]", ImeUtils.dumpCorrectionInfo(correcti onInfo)); | |
490 assertOnImeThread(); | |
491 // TODO(changwan): Auto-generated method stub | |
492 return false; | |
493 } | |
494 | |
495 /** | |
496 * @see InputConnection#clearMetaKeyStates(int) | |
497 */ | |
498 @Override | |
499 public boolean clearMetaKeyStates(int states) { | |
500 Log.d(TAG, "clearMetaKeyStates [%x]", states); | |
501 assertOnImeThread(); | |
502 // TODO(changwan): Auto-generated method stub | |
503 return false; | |
504 } | |
505 | |
506 /** | |
507 * @see InputConnection#reportFullscreenMode(boolean) | |
508 */ | |
509 @Override | |
510 public boolean reportFullscreenMode(boolean enabled) { | |
511 Log.d(TAG, "reportFullscreenMode [%b]", enabled); | |
512 // We ignore fullscreen mode for now. That's why we set | |
513 // EditorInfo.IME_FLAG_NO_FULLSCREEN in constructor. | |
514 // Note that this may be called on UI thread. | |
515 return false; | |
516 } | |
517 | |
518 /** | |
519 * @see InputConnection#performPrivateCommand(java.lang.String, android.os.B undle) | |
520 */ | |
521 @Override | |
522 public boolean performPrivateCommand(String action, Bundle data) { | |
523 Log.d(TAG, "performPrivateCommand [%s]", action); | |
524 assertOnImeThread(); | |
525 // TODO(changwan): Auto-generated method stub | |
526 return false; | |
527 } | |
528 | |
529 /** | |
530 * @see InputConnection#requestCursorUpdates(int) | |
531 */ | |
532 @Override | |
533 public boolean requestCursorUpdates(int cursorUpdateMode) { | |
534 Log.d(TAG, "requestCursorUpdates [%x]", cursorUpdateMode); | |
535 assertOnImeThread(); | |
536 // TODO(changwan): Auto-generated method stub | |
537 return false; | |
538 } | |
539 } | |
OLD | NEW |