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

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: fixed a crash for LatinIME 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 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() {
aelias_OOO_until_Jul13 2016/01/23 03:21:39 By removing the "else" around this and removing th
Changwan Ryu 2016/01/25 09:07:27 The problem was as follows: 1) IME-originating st
aelias_OOO_until_Jul13 2016/01/26 00:33:05 Got it. Sorry about not considering that when I p
Changwan Ryu 2016/01/26 03:25:28 Thanks, that's what I was considering, too. It was
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);
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 // TODO(changwan): check if there is a chance that this gets cal led
aelias_OOO_until_Jul13 2016/01/23 03:21:39 Yes, this looks like a race condition. I propose
Changwan Ryu 2016/01/26 03:25:28 Good idea. Done.
300 // before updateSelection from a nested command.
301 updateSelection(mBatchEditPendingState);
302 mBatchEditPendingState = null;
303 }
304 }
305 return mNumNestedBatchEdits != 0;
306 }
307
308 /**
309 * @see InputConnection#deleteSurroundingText(int, int)
310 */
311 @Override
312 public boolean deleteSurroundingText(final int beforeLength, final int after Length) {
313 Log.d(TAG, "deleteSurroundingText [%d %d]", beforeLength, afterLength);
314 assertOnImeThread();
315 ThreadUtils.postOnUiThread(new Runnable() {
316 @Override
317 public void run() {
318 mImeAdapter.deleteSurroundingText(beforeLength, afterLength);
319 }
320 });
321 return requestTextInputState() != null;
322 }
323
324 /**
325 * @see ChromiumBaseInputConnection#sendKeyEventOnUiThread(KeyEvent)
326 */
327 @Override
328 public boolean sendKeyEventOnUiThread(final KeyEvent event) {
329 ImeUtils.assertOnUiThread();
330 mThreadManager.post(new Runnable() {
331 @Override
332 public void run() {
333 sendKeyEvent(event);
334 }
335 });
336 return true;
337 }
338
339 /**
340 * @see InputConnection#sendKeyEvent(android.view.KeyEvent)
341 */
342 @Override
343 public boolean sendKeyEvent(final KeyEvent event) {
344 Log.d(TAG, "sendKeyEvent [%d %d]", event.getAction(), event.getKeyCode() );
345 assertOnImeThread();
346 ThreadUtils.postOnUiThread(new Runnable() {
347 @Override
348 public void run() {
349 mImeAdapter.sendKeyEvent(event);
350 }
351 });
352 return requestTextInputState() != null;
353 }
354
355 /**
356 * @see InputConnection#finishComposingText()
357 */
358 @Override
359 public boolean finishComposingText() {
360 Log.d(TAG, "finishComposingText");
361 // This is the only function that may be called on UI thread because
362 // of direct calls from InputMethodManager.
363
364 ThreadUtils.postOnUiThread(new Runnable() {
365 @Override
366 public void run() {
367 mImeAdapter.finishComposingText();
368 }
369 });
370
371 if (ThreadUtils.runningOnUiThread()) {
372 Log.d(TAG, "finishComposingText was called on UI thread.");
373 return true;
374 } else {
375 return requestTextInputState() != null;
376 }
377 }
378
379 /**
380 * @see InputConnection#setSelection(int, int)
381 */
382 @Override
383 public boolean setSelection(final int start, final int end) {
384 Log.d(TAG, "setSelection [%d %d]", start, end);
385 assertOnImeThread();
386 ThreadUtils.postOnUiThread(new Runnable() {
387 @Override
388 public void run() {
389 mImeAdapter.setEditableSelectionOffsets(start, end);
390 }
391 });
392 return requestTextInputState() != null;
393 }
394
395 /**
396 * @see InputConnection#setComposingRegion(int, int)
397 */
398 @Override
399 public boolean setComposingRegion(final int start, final int end) {
400 Log.d(TAG, "setComposingRegion [%d %d]", start, end);
401 assertOnImeThread();
402 ThreadUtils.postOnUiThread(new Runnable() {
403 @Override
404 public void run() {
405 mImeAdapter.setComposingRegion(start, end);
406 }
407 });
408 return requestTextInputState() != null;
409 }
410
411 /**
412 * @see InputConnection#getTextBeforeCursor(int, int)
413 */
414 @Override
415 public CharSequence getTextBeforeCursor(int maxChars, int flags) {
416 Log.d(TAG, "getTextBeforeCursor [%d %x]", maxChars, flags);
417 assertOnImeThread();
418 TextInputState textInputState = requestTextInputState();
419 if (textInputState == null) return null;
420 return textInputState.getTextBeforeSelection(maxChars);
421 }
422
423 /**
424 * @see InputConnection#getTextAfterCursor(int, int)
425 */
426 @Override
427 public CharSequence getTextAfterCursor(int maxChars, int flags) {
428 Log.d(TAG, "getTextAfterCursor [%d %x]", maxChars, flags);
429 assertOnImeThread();
430 TextInputState textInputState = requestTextInputState();
431 if (textInputState == null) return null;
432 return textInputState.getTextAfterSelection(maxChars);
433 }
434
435 /**
436 * @see InputConnection#getSelectedText(int)
437 */
438 @Override
439 public CharSequence getSelectedText(int flags) {
440 Log.d(TAG, "getSelectedText [%x]", flags);
441 assertOnImeThread();
442 TextInputState textInputState = requestTextInputState();
443 if (textInputState == null) return null;
444 return textInputState.getSelectedText();
445 }
446
447 @Override
448 public void moveCursorToSelectionEndOnUiThread() {
449 mThreadManager.post(new Runnable() {
450 @Override
451 public void run() {
452 TextInputState textInputState = requestTextInputState();
453 if (textInputState == null) return;
454 Range selection = textInputState.selection();
455 setSelection(selection.end(), selection.end());
456 }
457 });
458 }
459
460 /**
461 * @see InputConnection#getCursorCapsMode(int)
462 */
463 @Override
464 public int getCursorCapsMode(int reqModes) {
465 Log.d(TAG, "getCursorCapsMode [%x]", reqModes);
466 assertOnImeThread();
467 // TODO(changwan): Auto-generated method stub
468 return 0;
469 }
470
471 /**
472 * @see InputConnection#commitCompletion(android.view.inputmethod.Completion Info)
473 */
474 @Override
475 public boolean commitCompletion(CompletionInfo text) {
476 Log.d(TAG, "commitCompletion [%s]", text);
477 assertOnImeThread();
478 // TODO(changwan): Auto-generated method stub
479 return false;
480 }
481
482 /**
483 * @see InputConnection#commitCorrection(android.view.inputmethod.Correction Info)
484 */
485 @Override
486 public boolean commitCorrection(CorrectionInfo correctionInfo) {
487 Log.d(TAG, "commitCorrection [%s]", ImeUtils.dumpCorrectionInfo(correcti onInfo));
488 assertOnImeThread();
489 // TODO(changwan): Auto-generated method stub
490 return false;
491 }
492
493 /**
494 * @see InputConnection#clearMetaKeyStates(int)
495 */
496 @Override
497 public boolean clearMetaKeyStates(int states) {
498 Log.d(TAG, "clearMetaKeyStates [%x]", states);
499 assertOnImeThread();
500 // TODO(changwan): Auto-generated method stub
501 return false;
502 }
503
504 /**
505 * @see InputConnection#reportFullscreenMode(boolean)
506 */
507 @Override
508 public boolean reportFullscreenMode(boolean enabled) {
509 Log.d(TAG, "reportFullscreenMode [%b]", enabled);
510 // We ignore fullscreen mode for now. That's why we set
511 // EditorInfo.IME_FLAG_NO_FULLSCREEN in constructor.
512 // Note that this may be called on UI thread.
513 return false;
514 }
515
516 /**
517 * @see InputConnection#performPrivateCommand(java.lang.String, android.os.B undle)
518 */
519 @Override
520 public boolean performPrivateCommand(String action, Bundle data) {
521 Log.d(TAG, "performPrivateCommand [%s]", action);
522 assertOnImeThread();
523 // TODO(changwan): Auto-generated method stub
524 return false;
525 }
526
527 /**
528 * @see InputConnection#requestCursorUpdates(int)
529 */
530 @Override
531 public boolean requestCursorUpdates(int cursorUpdateMode) {
532 Log.d(TAG, "requestCursorUpdates [%x]", cursorUpdateMode);
533 assertOnImeThread();
534 // TODO(changwan): Auto-generated method stub
535 return false;
536 }
537 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698