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

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: unified ime tests, changed default values, fixed bugs, not wait for update if possible 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.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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698