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