Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(401)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java

Issue 1278593004: Introduce ThreadedInputConnection behind a switch (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: handles render crash and navigation Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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.KeyCharacterMap;
9 import android.view.KeyEvent;
10 import android.view.inputmethod.CompletionInfo;
11 import android.view.inputmethod.CorrectionInfo;
12 import android.view.inputmethod.EditorInfo;
13 import android.view.inputmethod.ExtractedText;
14 import android.view.inputmethod.ExtractedTextRequest;
15 import android.view.inputmethod.InputConnection;
16
17 import org.chromium.base.Log;
18 import org.chromium.base.ThreadUtils;
19
20 import java.util.concurrent.BlockingQueue;
21 import java.util.concurrent.LinkedBlockingQueue;
22
23 /**
24 * An implementation of {@link InputConnection} to communicate with external inp ut method
25 * apps. Note that it is running on IME thread (except for constructor and calls from ImeAdapter)
26 * such that it does not block UI thread and returns text values immediately aft er any change
27 * to them.
28 */
29 public class ThreadedInputConnection implements ChromiumBaseInputConnection {
30 private static final String TAG = "cr_Ime";
31
32 private final ImeAdapter mImeAdapter;
33 private final ThreadManager mThreadManager;
34 private int mNumNestedBatchEdits = 0;
Ted C 2016/02/02 23:14:43 zero is the default...no need for this.
Changwan Ryu 2016/02/11 16:21:08 Done.
35 private final BlockingQueue<TextInputState> mQueue = new LinkedBlockingQueue <>();
Ted C 2016/02/02 23:14:43 do we get a new TextInputState for every key strok
Changwan Ryu 2016/02/11 16:21:08 Hmm... I noticed that android.os.Message is using
36 private int mPendingAccent;
37 // Only accessible on UI thread.
38 private TextInputState mLastUpdatedTextInputState;
39
40
41 ThreadedInputConnection(ImeAdapter imeAdapter, ThreadManager threadManager) {
42 Log.d(TAG, "constructor");
43 ImeUtils.assertOnUiThread();
44 mImeAdapter = imeAdapter;
45 mThreadManager = threadManager;
46 }
47
48 void initializeOutAttrsOnUiThread(int inputType, int inputFlags, EditorInfo outAttrs) {
49 Log.d(TAG, "initializeOutAttrs");
50 ImeUtils.assertOnUiThread();
51 int initialSelStart = 0;
52 int initialSelEnd = 0;
53 mNumNestedBatchEdits = 0;
54 mPendingAccent = 0;
55 if (mLastUpdatedTextInputState != null) {
56 initialSelStart = mLastUpdatedTextInputState.selection().start();
57 initialSelEnd = mLastUpdatedTextInputState.selection().end();
58 }
59 ImeUtils.computeEditorInfo(inputType, inputFlags, initialSelStart, initi alSelEnd, outAttrs);
60 }
61
62 @Override
63 public void updateStateOnUiThread(final String text, final int selectionStar t,
64 final int selectionEnd, final int compositionStart, final int compos itionEnd,
65 boolean singleLine, final boolean isNonImeChange) {
66 ImeUtils.assertOnUiThread();
67
68 final TextInputState newState =
69 new TextInputState(text, new Range(selectionStart, selectionEnd) ,
70 new Range(compositionStart, compositionEnd), singleLine, !isNonImeChange);
71 Log.d(TAG, "updateState: %s", newState);
72 mLastUpdatedTextInputState = newState;
73
74 addToQueueOnUiThread(newState);
75 if (isNonImeChange) {
76 mThreadManager.post(new Runnable() {
77 @Override
78 public void run() {
79 checkQueueForNonImeUpdate();
80 }
81 });
82 }
83 }
84
85 /**
86 * @see ChromiumBaseInputConnection#getThreadManager()
87 */
88 @Override
89 public ThreadManager getThreadManager() {
90 return mThreadManager;
91 }
92
93 /**
94 * @see ChromiumBaseInputConnection#onRestartInputOnUiThread()
95 */
96 @Override
97 public void onRestartInputOnUiThread() {}
98
99 /**
100 * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent)
101 */
102 @Override
103 public boolean sendKeyEventOnUiThread(final KeyEvent event) {
104 ImeUtils.assertOnUiThread();
105 mThreadManager.post(new Runnable() {
106 @Override
107 public void run() {
108 sendKeyEvent(event);
109 }
110 });
111 return true;
112 }
113
114 /**
115 * @see ChromiumBaseInputConnection#moveCursorToSelectionEndOnUiThread()
116 */
117 @Override
118 public void moveCursorToSelectionEndOnUiThread() {
119 mThreadManager.post(new Runnable() {
120 @Override
121 public void run() {
122 TextInputState textInputState = requestTextInputState();
123 if (textInputState == null) return;
124 Range selection = textInputState.selection();
125 setSelection(selection.end(), selection.end());
126 }
127 });
128 }
129
130 @Override
131 public void unblockOnUiThread() {
132 Log.d(TAG, "unblock");
133 ImeUtils.assertOnUiThread();
134 addToQueueOnUiThread(new TextInputState.Unblocker());
135 mThreadManager.post(new Runnable() {
136 @Override
137 public void run() {
138 checkQueueForUnblocker();
139 }
140 });
141 }
142
143 private void checkQueueForNonImeUpdate() {
144 assertOnImeThread();
145 TextInputState state = mQueue.peek();
146 if (state == null) {
147 Log.d(TAG, "checkQueueForNonImeUpdate: no state");
148 return;
149 }
150 mQueue.poll();
151 ImeUtils.assertReally(!state.fromIme());
152 updateSelection(state);
153 Log.d(TAG, "checkQueueForNonImeUpdate done: %d", mQueue.size());
154 }
155
156 private void checkQueueForUnblocker() {
157 Log.d(TAG, "checkQueueForUnblocker");
158 assertOnImeThread();
159 // Clear all the states in the queue.
160 while (true) {
161 TextInputState state = mQueue.poll();
162 if (state == null) return;
163 if (state.shouldUnblock()) return;
164 }
165 }
166
167 private void updateSelection(TextInputState textInputState) {
168 assertOnImeThread();
169 if (mNumNestedBatchEdits != 0) return;
170 ImeUtils.assertReally(textInputState != null);
171 Range selection = textInputState.selection();
172 Range composition = textInputState.composition();
173 mImeAdapter.updateSelection(
174 selection.start(), selection.end(), composition.start(), composi tion.end());
175 }
176
177 private TextInputState requestTextInputState() {
178 Log.d(TAG, "requestTextInputState");
179 ThreadUtils.postOnUiThread(new Runnable() {
180 @Override
181 public void run() {
182 boolean result = mImeAdapter.requestTextInputStateUpdate();
183 if (!result) unblockOnUiThread();
184 }
185 });
186 return blockAndGetStateUpdate();
187 }
188
189 private void addToQueueOnUiThread(TextInputState textInputState) {
190 ImeUtils.assertOnUiThread();
191 try {
192 mQueue.put(textInputState);
193 } catch (InterruptedException e) {
194 Log.e(TAG, "addToQueueOnUiThread interrupted", e);
195 }
196 Log.d(TAG, "addToQueueOnUiThread finished: %d", mQueue.size());
197 }
198
199 /**
200 * @return BlockingQueue for white box unit testing.
201 */
202 BlockingQueue<TextInputState> getQueueForTest() {
203 return mQueue;
204 }
205
206 private void assertOnImeThread() {
207 ImeUtils.assertReally(mThreadManager.runningOnThisThread());
208 }
209
210 /**
211 * Block until we get the expected state update.
212 * @return TextInputState if we get it successfully. null otherwise.
213 */
214 private TextInputState blockAndGetStateUpdate() {
215 Log.d(TAG, "blockAndGetStateUpdate");
216 assertOnImeThread();
217 boolean shouldUpdateSelection = false;
218 while (true) {
219 TextInputState state;
220 try {
221 state = mQueue.take();
222 } catch (InterruptedException e) {
223 e.printStackTrace();
224 return null;
225 }
226 if (state == null) return null;
227 if (state.fromIme()) {
228 if (state.shouldUnblock()) {
229 Log.d(TAG, "blockAndGetStateUpdate: unblocked");
230 return null;
231 }
232 if (shouldUpdateSelection) updateSelection(state);
233 Log.d(TAG, "blockAndGetStateUpdate done: %d", mQueue.size());
234 return state;
235 }
236 // Ignore when state is not from IME, but make sure to update state when we handle
237 // state from IME.
238 shouldUpdateSelection = true;
239 }
240 }
241
242 private void notifyUserAction() {
243 ThreadUtils.postOnUiThread(new Runnable() {
244 @Override
245 public void run() {
246 mImeAdapter.notifyUserAction();
247 }
248 });
249 }
250
251 /**
252 * @see InputConnection#setComposingText(java.lang.CharSequence, int)
253 */
254 @Override
255 public boolean setComposingText(final CharSequence text, final int newCursor Position) {
256 Log.d(TAG, "setComposingText [%s] [%d]", text, newCursorPosition);
257 assertOnImeThread();
258 cancelCombiningAccent();
259 ThreadUtils.postOnUiThread(new Runnable() {
260 @Override
261 public void run() {
262 mImeAdapter.sendCompositionToNative(text, newCursorPosition, fal se);
263 }
264 });
265 notifyUserAction();
266 return true;
267 }
268
269 /**
270 * @see InputConnection#commitText(java.lang.CharSequence, int)
271 */
272 @Override
273 public boolean commitText(final CharSequence text, final int newCursorPositi on) {
274 Log.d(TAG, "commitText [%s] [%d]", text, newCursorPosition);
275 assertOnImeThread();
276 cancelCombiningAccent();
277 ThreadUtils.postOnUiThread(new Runnable() {
278 @Override
279 public void run() {
280 mImeAdapter.sendCompositionToNative(text, newCursorPosition, tex t.length() > 0);
281 }
282 });
283 notifyUserAction();
284 return true;
285 }
286
287 /**
288 * @see InputConnection#performEditorAction(int)
289 */
290 @Override
291 public boolean performEditorAction(final int actionCode) {
292 Log.d(TAG, "performEditorAction [%d]", actionCode);
293 assertOnImeThread();
294 ThreadUtils.postOnUiThread(new Runnable() {
295 @Override
296 public void run() {
297 mImeAdapter.performEditorAction(actionCode);
298 }
299 });
300 return true;
301 }
302
303 /**
304 * @see InputConnection#performContextMenuAction(int)
305 */
306 @Override
307 public boolean performContextMenuAction(final int id) {
308 Log.d(TAG, "performContextMenuAction [%d]", id);
309 assertOnImeThread();
310 ThreadUtils.postOnUiThread(new Runnable() {
311 @Override
312 public void run() {
313 mImeAdapter.performContextMenuAction(id);
314 }
315 });
316 return true;
317 }
318
319 /**
320 * @see InputConnection#getExtractedText(android.view.inputmethod.ExtractedT extRequest,
321 * int)
322 */
323 @Override
324 public ExtractedText getExtractedText(ExtractedTextRequest request, int flag s) {
325 Log.d(TAG, "getExtractedText");
326 assertOnImeThread();
327 TextInputState textInputState = requestTextInputState();
328 if (textInputState == null) return null;
329 ExtractedText extractedText = new ExtractedText();
330 extractedText.text = textInputState.text();
331 extractedText.partialEndOffset = textInputState.text().length();
332 extractedText.selectionStart = textInputState.selection().start();
333 extractedText.selectionEnd = textInputState.selection().end();
334 extractedText.flags = textInputState.singleLine() ? ExtractedText.FLAG_S INGLE_LINE : 0;
335 return extractedText;
336 }
337
338 /**
339 * @see InputConnection#beginBatchEdit()
340 */
341 @Override
342 public boolean beginBatchEdit() {
343 Log.d(TAG, "beginBatchEdit [%b]", (mNumNestedBatchEdits == 0));
344 assertOnImeThread();
345 mNumNestedBatchEdits++;
346 return true;
347 }
348
349 /**
350 * @see InputConnection#endBatchEdit()
351 */
352 @Override
353 public boolean endBatchEdit() {
354 assertOnImeThread();
355 if (mNumNestedBatchEdits == 0) return false;
356 --mNumNestedBatchEdits;
357 Log.d(TAG, "endBatchEdit [%b]", (mNumNestedBatchEdits == 0));
358 if (mNumNestedBatchEdits == 0) {
359 updateSelection(requestTextInputState());
360 }
361 return mNumNestedBatchEdits != 0;
362 }
363
364 /**
365 * @see InputConnection#deleteSurroundingText(int, int)
366 */
367 @Override
368 public boolean deleteSurroundingText(final int beforeLength, final int after Length) {
369 Log.d(TAG, "deleteSurroundingText [%d %d]", beforeLength, afterLength);
370 assertOnImeThread();
371 if (mPendingAccent != 0) {
372 finishComposingText();
373 }
374 ThreadUtils.postOnUiThread(new Runnable() {
375 @Override
376 public void run() {
377 mImeAdapter.deleteSurroundingText(beforeLength, afterLength);
378 }
379 });
380 return true;
381 }
382
383 /**
384 * @see InputConnection#sendKeyEvent(android.view.KeyEvent)
385 */
386 @Override
387 public boolean sendKeyEvent(final KeyEvent event) {
388 Log.d(TAG, "sendKeyEvent [%d %d]", event.getAction(), event.getKeyCode() );
389 assertOnImeThread();
390
391 if (handleCombiningAccent(event)) return true;
392
393 ThreadUtils.postOnUiThread(new Runnable() {
394 @Override
395 public void run() {
396 mImeAdapter.sendKeyEvent(event);
397 }
398 });
399 notifyUserAction();
400 return true;
401 }
402
403 private boolean handleCombiningAccent(final KeyEvent event) {
404 // TODO(changwan): this will break the current composition. check if we can
405 // implement it in the renderer instead.
406 int action = event.getAction();
407 int unicodeChar = event.getUnicodeChar();
408
409 if (action != KeyEvent.ACTION_DOWN) return false;
410 if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
411 int pendingAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_M ASK;
412 StringBuilder builder = new StringBuilder();
413 builder.appendCodePoint(pendingAccent);
414 setComposingText(builder.toString(), 1);
415 mPendingAccent = pendingAccent;
416 return true;
417 } else if (mPendingAccent != 0 && unicodeChar != 0) {
418 int combined = KeyEvent.getDeadChar(mPendingAccent, unicodeChar);
419 if (combined != 0) {
420 StringBuilder builder = new StringBuilder();
421 builder.appendCodePoint(combined);
422 commitText(builder.toString(), 1);
423 return true;
424 }
425 // Noncombinable character; commit the accent character and fall thr ough to sending
426 // the key event for the character afterwards.
427 finishComposingText();
428 }
429 return false;
430 }
431
432 private void cancelCombiningAccent() {
433 mPendingAccent = 0;
434 }
435
436 /**
437 * @see InputConnection#finishComposingText()
438 */
439 @Override
440 public boolean finishComposingText() {
441 Log.d(TAG, "finishComposingText");
442 cancelCombiningAccent();
443 // This is the only function that may be called on UI thread because
444 // of direct calls from InputMethodManager.
445 ThreadUtils.postOnUiThread(new Runnable() {
446 @Override
447 public void run() {
448 mImeAdapter.finishComposingText();
449 }
450 });
451 return true;
452 }
453
454 /**
455 * @see InputConnection#setSelection(int, int)
456 */
457 @Override
458 public boolean setSelection(final int start, final int end) {
459 Log.d(TAG, "setSelection [%d %d]", start, end);
460 assertOnImeThread();
461 ThreadUtils.postOnUiThread(new Runnable() {
462 @Override
463 public void run() {
464 mImeAdapter.setEditableSelectionOffsets(start, end);
465 }
466 });
467 return true;
468 }
469
470 /**
471 * @see InputConnection#setComposingRegion(int, int)
472 */
473 @Override
474 public boolean setComposingRegion(final int start, final int end) {
475 Log.d(TAG, "setComposingRegion [%d %d]", start, end);
476 assertOnImeThread();
477 ThreadUtils.postOnUiThread(new Runnable() {
478 @Override
479 public void run() {
480 mImeAdapter.setComposingRegion(start, end);
481 }
482 });
483 return true;
484 }
485
486 /**
487 * @see InputConnection#getTextBeforeCursor(int, int)
488 */
489 @Override
490 public CharSequence getTextBeforeCursor(int maxChars, int flags) {
491 Log.d(TAG, "getTextBeforeCursor [%d %x]", maxChars, flags);
492 assertOnImeThread();
493 TextInputState textInputState = requestTextInputState();
494 if (textInputState == null) return null;
495 return textInputState.getTextBeforeSelection(maxChars);
496 }
497
498 /**
499 * @see InputConnection#getTextAfterCursor(int, int)
500 */
501 @Override
502 public CharSequence getTextAfterCursor(int maxChars, int flags) {
503 Log.d(TAG, "getTextAfterCursor [%d %x]", maxChars, flags);
504 assertOnImeThread();
505 TextInputState textInputState = requestTextInputState();
506 if (textInputState == null) return null;
507 return textInputState.getTextAfterSelection(maxChars);
508 }
509
510 /**
511 * @see InputConnection#getSelectedText(int)
512 */
513 @Override
514 public CharSequence getSelectedText(int flags) {
515 Log.d(TAG, "getSelectedText [%x]", flags);
516 assertOnImeThread();
517 TextInputState textInputState = requestTextInputState();
518 if (textInputState == null) return null;
519 return textInputState.getSelectedText();
520 }
521
522 /**
523 * @see InputConnection#getCursorCapsMode(int)
524 */
525 @Override
526 public int getCursorCapsMode(int reqModes) {
527 Log.d(TAG, "getCursorCapsMode [%x]", reqModes);
528 assertOnImeThread();
529 // TODO(changwan): Auto-generated method stub
530 return 0;
531 }
532
533 /**
534 * @see InputConnection#commitCompletion(android.view.inputmethod.Completion Info)
535 */
536 @Override
537 public boolean commitCompletion(CompletionInfo text) {
538 Log.d(TAG, "commitCompletion [%s]", text);
539 assertOnImeThread();
540 // TODO(changwan): Auto-generated method stub
541 return false;
542 }
543
544 /**
545 * @see InputConnection#commitCorrection(android.view.inputmethod.Correction Info)
546 */
547 @Override
548 public boolean commitCorrection(CorrectionInfo correctionInfo) {
549 Log.d(TAG, "commitCorrection [%s]", ImeUtils.dumpCorrectionInfo(correcti onInfo));
550 assertOnImeThread();
551 // TODO(changwan): Auto-generated method stub
552 return false;
553 }
554
555 /**
556 * @see InputConnection#clearMetaKeyStates(int)
557 */
558 @Override
559 public boolean clearMetaKeyStates(int states) {
560 Log.d(TAG, "clearMetaKeyStates [%x]", states);
561 assertOnImeThread();
562 // TODO(changwan): Auto-generated method stub
563 return false;
564 }
565
566 /**
567 * @see InputConnection#reportFullscreenMode(boolean)
568 */
569 @Override
570 public boolean reportFullscreenMode(boolean enabled) {
571 Log.d(TAG, "reportFullscreenMode [%b]", enabled);
572 // We ignore fullscreen mode for now. That's why we set
573 // EditorInfo.IME_FLAG_NO_FULLSCREEN in constructor.
574 // Note that this may be called on UI thread.
575 return false;
576 }
577
578 /**
579 * @see InputConnection#performPrivateCommand(java.lang.String, android.os.B undle)
580 */
581 @Override
582 public boolean performPrivateCommand(String action, Bundle data) {
583 Log.d(TAG, "performPrivateCommand [%s]", action);
584 assertOnImeThread();
585 // TODO(changwan): Auto-generated method stub
586 return false;
587 }
588
589 /**
590 * @see InputConnection#requestCursorUpdates(int)
591 */
592 @Override
593 public boolean requestCursorUpdates(int cursorUpdateMode) {
594 Log.d(TAG, "requestCursorUpdates [%x]", cursorUpdateMode);
595 assertOnImeThread();
596 // TODO(changwan): Auto-generated method stub
597 return false;
598 }
599 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698