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

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

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

Powered by Google App Engine
This is Rietveld 408576698