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 mBatchEditPendingState; | |
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 initializeOutAttrsOnUiThread(int inputType, int inputFlags, EditorInfo outAttrs) { | |
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 final TextInputState newState = | |
93 new TextInputState(text, new Range(selectionStart, selectionEnd) , | |
94 new Range(compositionStart, compositionEnd), singleLine) ; | |
95 mLastUpdatedTextInputState = newState; | |
96 | |
97 if (!isNonImeChange) { | |
98 addToQueueOnUiThread(newState); | |
99 } | |
100 | |
101 mThreadManager.post(new Runnable() { | |
102 @Override | |
103 public void run() { | |
104 updateSelection(newState); | |
105 } | |
106 }); | |
107 Log.d(TAG, "updateState finished"); | |
108 } | |
109 | |
110 @Override | |
111 public ThreadManager getThreadManager() { | |
112 return mThreadManager; | |
113 } | |
114 | |
115 @Override | |
116 public void onRestartInputOnUiThread() {} | |
117 | |
118 private void updateSelection(TextInputState textInputState) { | |
119 assertOnImeThread(); | |
120 if (mNumNestedBatchEdits != 0) { | |
121 mBatchEditPendingState = textInputState; | |
122 return; | |
123 } | |
124 ImeUtils.assertReally(textInputState != null); | |
aelias_OOO_until_Jul13
2016/01/22 23:34:49
I applied the patch locally on ToT r371021 on Nexu
aelias_OOO_until_Jul13
2016/01/23 03:21:39
Thanks, it works fine now.
| |
125 Range selection = textInputState.selection(); | |
126 Range composition = textInputState.composition(); | |
127 mImeAdapter.updateSelection( | |
128 selection.start(), selection.end(), composition.start(), composi tion.end()); | |
129 } | |
130 | |
131 private TextInputState requestTextInputState() { | |
132 Log.d(TAG, "requestTextInputState"); | |
133 ThreadUtils.postOnUiThread(new Runnable() { | |
134 @Override | |
135 public void run() { | |
136 boolean result = mImeAdapter.requestTextInputStateUpdate(); | |
137 if (!result) unblock(); | |
138 } | |
139 }); | |
140 return blockAndGetStateUpdate(); | |
141 } | |
142 | |
143 private void addToQueueOnUiThread(TextInputState textInputState) { | |
144 ImeUtils.assertOnUiThread(); | |
145 try { | |
146 mQueue.put(textInputState); | |
147 } catch (InterruptedException e) { | |
148 Log.e(TAG, "onImeOriginatingStateUpdateOnUiThread interrupted", e); | |
149 } | |
150 Log.d(TAG, "onImeOriginatingStateUpdateOnUiThread finished: %d", mQueue. size()); | |
151 } | |
152 | |
153 /** | |
154 * Unblock the blockAndGetStateUpdate() function if we found that we will | |
155 * never get state update. | |
156 */ | |
157 private void unblock() { | |
158 ImeUtils.assertOnUiThread(); | |
159 addToQueueOnUiThread(new TextInputState.Unblocker()); | |
160 } | |
161 | |
162 private void assertOnImeThread() { | |
163 ImeUtils.assertReally(mThreadManager.runningOnThisThread()); | |
164 } | |
165 | |
166 /** | |
167 * Block until we get the expected state update. | |
168 * @return TextInputState if we get it successfully. null otherwise. | |
169 */ | |
170 private TextInputState blockAndGetStateUpdate() { | |
171 assertOnImeThread(); | |
172 while (true) { | |
173 TextInputState result = mQueue.poll(); | |
174 if (result != null) { | |
175 if (result.shouldUnblock()) { | |
176 Log.d(TAG, "blockAndGetStateUpdate: unblocked"); | |
177 return null; | |
178 } | |
179 Log.d(TAG, "blockAndGetStateUpdate finished: %s", result.toStrin g()); | |
180 return result; | |
181 } | |
182 } | |
183 } | |
184 | |
185 /** | |
186 * @see InputConnection#setComposingText(java.lang.CharSequence, int) | |
187 */ | |
188 @Override | |
189 public boolean setComposingText(final CharSequence text, final int newCursor Position) { | |
190 Log.d(TAG, "setComposingText [%s] [%d]", text, newCursorPosition); | |
191 assertOnImeThread(); | |
192 ThreadUtils.postOnUiThread(new Runnable() { | |
193 @Override | |
194 public void run() { | |
195 mImeAdapter.sendCompositionToNative(text, newCursorPosition, fal se); | |
196 } | |
197 }); | |
198 | |
199 return requestTextInputState() != null; | |
200 } | |
201 | |
202 /** | |
203 * @see InputConnection#commitText(java.lang.CharSequence, int) | |
204 */ | |
205 @Override | |
206 public boolean commitText(final CharSequence text, final int newCursorPositi on) { | |
207 Log.d(TAG, "commitText [%s] [%d]", text, newCursorPosition); | |
208 assertOnImeThread(); | |
209 ThreadUtils.postOnUiThread(new Runnable() { | |
210 @Override | |
211 public void run() { | |
212 mImeAdapter.sendCompositionToNative(text, newCursorPosition, tex t.length() > 0); | |
213 } | |
214 }); | |
215 return requestTextInputState() != null; | |
216 } | |
217 | |
218 /** | |
219 * @see InputConnection#performEditorAction(int) | |
220 */ | |
221 @Override | |
222 public boolean performEditorAction(final int actionCode) { | |
223 Log.d(TAG, "performEditorAction [%d]", actionCode); | |
224 assertOnImeThread(); | |
225 try { | |
226 return ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() { | |
227 @Override | |
228 public Boolean call() { | |
229 return mImeAdapter.performEditorAction(actionCode); | |
230 } | |
231 }); | |
232 } catch (ExecutionException e) { | |
233 e.printStackTrace(); | |
234 return false; | |
235 } | |
236 } | |
237 | |
238 /** | |
239 * @see InputConnection#performContextMenuAction(int) | |
240 */ | |
241 @Override | |
242 public boolean performContextMenuAction(final int id) { | |
243 Log.d(TAG, "performContextMenuAction [%d]", id); | |
244 assertOnImeThread(); | |
245 try { | |
246 return ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() { | |
247 @Override | |
248 public Boolean call() { | |
249 return mImeAdapter.performContextMenuAction(id); | |
250 } | |
251 }); | |
252 } catch (ExecutionException e) { | |
253 e.printStackTrace(); | |
254 return false; | |
255 } | |
256 } | |
257 | |
258 /** | |
259 * @see InputConnection#getExtractedText(android.view.inputmethod.ExtractedT extRequest, | |
260 * int) | |
261 */ | |
262 @Override | |
263 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag s) { | |
264 Log.d(TAG, "getExtractedText"); | |
265 assertOnImeThread(); | |
266 TextInputState textInputState = requestTextInputState(); | |
267 if (textInputState == null) return null; | |
268 ExtractedText extractedText = new ExtractedText(); | |
269 extractedText.text = textInputState.text(); | |
270 extractedText.partialEndOffset = textInputState.text().length(); | |
271 extractedText.selectionStart = textInputState.selection().start(); | |
272 extractedText.selectionEnd = textInputState.selection().end(); | |
273 extractedText.flags = textInputState.singleLine() ? ExtractedText.FLAG_S INGLE_LINE : 0; | |
274 return extractedText; | |
275 } | |
276 | |
277 /** | |
278 * @see InputConnection#beginBatchEdit() | |
279 */ | |
280 @Override | |
281 public boolean beginBatchEdit() { | |
282 Log.d(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0)); | |
283 assertOnImeThread(); | |
284 mNumNestedBatchEdits++; | |
285 return true; | |
286 } | |
287 | |
288 /** | |
289 * @see InputConnection#endBatchEdit() | |
290 */ | |
291 @Override | |
292 public boolean endBatchEdit() { | |
293 assertOnImeThread(); | |
294 if (mNumNestedBatchEdits == 0) return false; | |
295 --mNumNestedBatchEdits; | |
296 Log.d(TAG, "endBatchEdit [%b]", (mNumNestedBatchEdits == 0)); | |
297 if (mNumNestedBatchEdits == 0) { | |
298 if (mBatchEditPendingState != null) { | |
299 // Make sure that this gets called after any other state update. | |
300 mThreadManager.post(new Runnable() { | |
301 @Override | |
302 public void run() { | |
303 updateSelection(mBatchEditPendingState); | |
304 } | |
305 }); | |
306 mBatchEditPendingState = null; | |
307 } | |
308 } | |
309 return mNumNestedBatchEdits != 0; | |
310 } | |
311 | |
312 /** | |
313 * @see InputConnection#deleteSurroundingText(int, int) | |
314 */ | |
315 @Override | |
316 public boolean deleteSurroundingText(final int beforeLength, final int after Length) { | |
317 Log.d(TAG, "deleteSurroundingText [%d %d]", beforeLength, afterLength); | |
318 assertOnImeThread(); | |
319 ThreadUtils.postOnUiThread(new Runnable() { | |
320 @Override | |
321 public void run() { | |
322 mImeAdapter.deleteSurroundingText(beforeLength, afterLength); | |
323 } | |
324 }); | |
325 return requestTextInputState() != null; | |
326 } | |
327 | |
328 /** | |
329 * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent) | |
330 */ | |
331 @Override | |
332 public boolean sendKeyEventOnUiThread(final KeyEvent event) { | |
333 ImeUtils.assertOnUiThread(); | |
334 mThreadManager.post(new Runnable() { | |
335 @Override | |
336 public void run() { | |
337 sendKeyEvent(event); | |
338 } | |
339 }); | |
340 return true; | |
341 } | |
342 | |
343 /** | |
344 * @see InputConnection#sendKeyEvent(android.view.KeyEvent) | |
345 */ | |
346 @Override | |
347 public boolean sendKeyEvent(final KeyEvent event) { | |
348 Log.d(TAG, "sendKeyEvent [%d %d]", event.getAction(), event.getKeyCode() ); | |
349 assertOnImeThread(); | |
350 ThreadUtils.postOnUiThread(new Runnable() { | |
351 @Override | |
352 public void run() { | |
353 mImeAdapter.sendKeyEvent(event); | |
354 } | |
355 }); | |
356 return requestTextInputState() != null; | |
357 } | |
358 | |
359 /** | |
360 * @see InputConnection#finishComposingText() | |
361 */ | |
362 @Override | |
363 public boolean finishComposingText() { | |
364 Log.d(TAG, "finishComposingText"); | |
365 // This is the only function that may be called on UI thread because | |
366 // of direct calls from InputMethodManager. | |
367 | |
368 ThreadUtils.postOnUiThread(new Runnable() { | |
369 @Override | |
370 public void run() { | |
371 mImeAdapter.finishComposingText(); | |
372 } | |
373 }); | |
374 | |
375 if (ThreadUtils.runningOnUiThread()) { | |
376 Log.d(TAG, "finishComposingText was called on UI thread."); | |
377 return true; | |
378 } else { | |
379 return requestTextInputState() != null; | |
380 } | |
381 } | |
382 | |
383 /** | |
384 * @see InputConnection#setSelection(int, int) | |
385 */ | |
386 @Override | |
387 public boolean setSelection(final int start, final int end) { | |
388 Log.d(TAG, "setSelection [%d %d]", start, end); | |
389 assertOnImeThread(); | |
390 ThreadUtils.postOnUiThread(new Runnable() { | |
391 @Override | |
392 public void run() { | |
393 mImeAdapter.setEditableSelectionOffsets(start, end); | |
394 } | |
395 }); | |
396 return requestTextInputState() != null; | |
397 } | |
398 | |
399 /** | |
400 * @see InputConnection#setComposingRegion(int, int) | |
401 */ | |
402 @Override | |
403 public boolean setComposingRegion(final int start, final int end) { | |
404 Log.d(TAG, "setComposingRegion [%d %d]", start, end); | |
405 assertOnImeThread(); | |
406 ThreadUtils.postOnUiThread(new Runnable() { | |
407 @Override | |
408 public void run() { | |
409 mImeAdapter.setComposingRegion(start, end); | |
410 } | |
411 }); | |
412 return requestTextInputState() != null; | |
413 } | |
414 | |
415 /** | |
416 * @see InputConnection#getTextBeforeCursor(int, int) | |
417 */ | |
418 @Override | |
419 public CharSequence getTextBeforeCursor(int maxChars, int flags) { | |
420 Log.d(TAG, "getTextBeforeCursor [%d %x]", maxChars, flags); | |
421 assertOnImeThread(); | |
422 TextInputState textInputState = requestTextInputState(); | |
423 if (textInputState == null) return null; | |
424 return textInputState.getTextBeforeSelection(maxChars); | |
425 } | |
426 | |
427 /** | |
428 * @see InputConnection#getTextAfterCursor(int, int) | |
429 */ | |
430 @Override | |
431 public CharSequence getTextAfterCursor(int maxChars, int flags) { | |
432 Log.d(TAG, "getTextAfterCursor [%d %x]", maxChars, flags); | |
433 assertOnImeThread(); | |
434 TextInputState textInputState = requestTextInputState(); | |
435 if (textInputState == null) return null; | |
436 return textInputState.getTextAfterSelection(maxChars); | |
437 } | |
438 | |
439 /** | |
440 * @see InputConnection#getSelectedText(int) | |
441 */ | |
442 @Override | |
443 public CharSequence getSelectedText(int flags) { | |
444 Log.d(TAG, "getSelectedText [%x]", flags); | |
445 assertOnImeThread(); | |
446 TextInputState textInputState = requestTextInputState(); | |
447 if (textInputState == null) return null; | |
448 return textInputState.getSelectedText(); | |
449 } | |
450 | |
451 @Override | |
452 public void moveCursorToSelectionEndOnUiThread() { | |
453 mThreadManager.post(new Runnable() { | |
454 @Override | |
455 public void run() { | |
456 TextInputState textInputState = requestTextInputState(); | |
457 if (textInputState == null) return; | |
458 Range selection = textInputState.selection(); | |
459 setSelection(selection.end(), selection.end()); | |
460 } | |
461 }); | |
462 } | |
463 | |
464 /** | |
465 * @see InputConnection#getCursorCapsMode(int) | |
466 */ | |
467 @Override | |
468 public int getCursorCapsMode(int reqModes) { | |
469 Log.d(TAG, "getCursorCapsMode [%x]", reqModes); | |
470 assertOnImeThread(); | |
471 // TODO(changwan): Auto-generated method stub | |
472 return 0; | |
473 } | |
474 | |
475 /** | |
476 * @see InputConnection#commitCompletion(android.view.inputmethod.Completion Info) | |
477 */ | |
478 @Override | |
479 public boolean commitCompletion(CompletionInfo text) { | |
480 Log.d(TAG, "commitCompletion [%s]", text); | |
481 assertOnImeThread(); | |
482 // TODO(changwan): Auto-generated method stub | |
483 return false; | |
484 } | |
485 | |
486 /** | |
487 * @see InputConnection#commitCorrection(android.view.inputmethod.Correction Info) | |
488 */ | |
489 @Override | |
490 public boolean commitCorrection(CorrectionInfo correctionInfo) { | |
491 Log.d(TAG, "commitCorrection [%s]", ImeUtils.dumpCorrectionInfo(correcti onInfo)); | |
492 assertOnImeThread(); | |
493 // TODO(changwan): Auto-generated method stub | |
494 return false; | |
495 } | |
496 | |
497 /** | |
498 * @see InputConnection#clearMetaKeyStates(int) | |
499 */ | |
500 @Override | |
501 public boolean clearMetaKeyStates(int states) { | |
502 Log.d(TAG, "clearMetaKeyStates [%x]", states); | |
503 assertOnImeThread(); | |
504 // TODO(changwan): Auto-generated method stub | |
505 return false; | |
506 } | |
507 | |
508 /** | |
509 * @see InputConnection#reportFullscreenMode(boolean) | |
510 */ | |
511 @Override | |
512 public boolean reportFullscreenMode(boolean enabled) { | |
513 Log.d(TAG, "reportFullscreenMode [%b]", enabled); | |
514 // We ignore fullscreen mode for now. That's why we set | |
515 // EditorInfo.IME_FLAG_NO_FULLSCREEN in constructor. | |
516 // Note that this may be called on UI thread. | |
517 return false; | |
518 } | |
519 | |
520 /** | |
521 * @see InputConnection#performPrivateCommand(java.lang.String, android.os.B undle) | |
522 */ | |
523 @Override | |
524 public boolean performPrivateCommand(String action, Bundle data) { | |
525 Log.d(TAG, "performPrivateCommand [%s]", action); | |
526 assertOnImeThread(); | |
527 // TODO(changwan): Auto-generated method stub | |
528 return false; | |
529 } | |
530 | |
531 /** | |
532 * @see InputConnection#requestCursorUpdates(int) | |
533 */ | |
534 @Override | |
535 public boolean requestCursorUpdates(int cursorUpdateMode) { | |
536 Log.d(TAG, "requestCursorUpdates [%x]", cursorUpdateMode); | |
537 assertOnImeThread(); | |
538 // TODO(changwan): Auto-generated method stub | |
539 return false; | |
540 } | |
541 } | |
OLD | NEW |