2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.internal.view;
19 import com.android.internal.annotations.GuardedBy;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.os.RemoteException;
28 import android.util.Log;
29 import android.view.KeyEvent;
30 import android.view.inputmethod.CompletionInfo;
31 import android.view.inputmethod.CorrectionInfo;
32 import android.view.inputmethod.ExtractedTextRequest;
33 import android.view.inputmethod.InputConnection;
34 import android.view.inputmethod.InputConnectionInspector;
35 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
36 import android.view.inputmethod.InputContentInfo;
38 public abstract class IInputConnectionWrapper extends IInputContext.Stub {
39 static final String TAG = "IInputConnectionWrapper";
41 private static final int DO_GET_TEXT_AFTER_CURSOR = 10;
42 private static final int DO_GET_TEXT_BEFORE_CURSOR = 20;
43 private static final int DO_GET_SELECTED_TEXT = 25;
44 private static final int DO_GET_CURSOR_CAPS_MODE = 30;
45 private static final int DO_GET_EXTRACTED_TEXT = 40;
46 private static final int DO_COMMIT_TEXT = 50;
47 private static final int DO_COMMIT_COMPLETION = 55;
48 private static final int DO_COMMIT_CORRECTION = 56;
49 private static final int DO_SET_SELECTION = 57;
50 private static final int DO_PERFORM_EDITOR_ACTION = 58;
51 private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;
52 private static final int DO_SET_COMPOSING_TEXT = 60;
53 private static final int DO_SET_COMPOSING_REGION = 63;
54 private static final int DO_FINISH_COMPOSING_TEXT = 65;
55 private static final int DO_SEND_KEY_EVENT = 70;
56 private static final int DO_DELETE_SURROUNDING_TEXT = 80;
57 private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81;
58 private static final int DO_BEGIN_BATCH_EDIT = 90;
59 private static final int DO_END_BATCH_EDIT = 95;
60 private static final int DO_REPORT_FULLSCREEN_MODE = 100;
61 private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
62 private static final int DO_CLEAR_META_KEY_STATES = 130;
63 private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
64 private static final int DO_CLOSE_CONNECTION = 150;
65 private static final int DO_COMMIT_CONTENT = 160;
69 private InputConnection mInputConnection;
71 private Looper mMainLooper;
73 private Object mLock = new Object();
75 private boolean mFinished = false;
77 private String mInputMethodId;
79 static class SomeArgs {
82 IInputContextCallback callback;
86 class MyHandler extends Handler {
87 MyHandler(Looper looper) {
92 public void handleMessage(Message msg) {
97 public IInputConnectionWrapper(Looper mainLooper, @NonNull InputConnection inputConnection) {
98 mInputConnection = inputConnection;
99 mMainLooper = mainLooper;
100 mH = new MyHandler(mMainLooper);
104 public InputConnection getInputConnection() {
105 synchronized (mLock) {
106 return mInputConnection;
110 protected boolean isFinished() {
111 synchronized (mLock) {
116 public String getInputMethodId() {
117 synchronized (mLock) {
118 return mInputMethodId;
122 public void setInputMethodId(final String inputMethodId) {
123 synchronized (mLock) {
124 mInputMethodId = inputMethodId;
128 abstract protected boolean isActive();
131 * Called when the user took some actions that should be taken into consideration to update the
132 * LRU list for input method rotation.
134 abstract protected void onUserAction();
137 * Called when the input method started or stopped full-screen mode.
138 * @param enabled {@code true} if the input method starts full-screen mode.
139 * @param calledInBackground {@code true} if this input connection is in a state when incoming
140 * events are usually ignored.
142 abstract protected void onReportFullscreenMode(boolean enabled, boolean calledInBackground);
144 public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
145 dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
148 public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
149 dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
152 public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
153 dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
156 public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
157 dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
160 public void getExtractedText(ExtractedTextRequest request,
161 int flags, int seq, IInputContextCallback callback) {
162 dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
163 request, seq, callback));
166 public void commitText(CharSequence text, int newCursorPosition) {
167 dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
170 public void commitCompletion(CompletionInfo text) {
171 dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text));
174 public void commitCorrection(CorrectionInfo info) {
175 dispatchMessage(obtainMessageO(DO_COMMIT_CORRECTION, info));
178 public void setSelection(int start, int end) {
179 dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end));
182 public void performEditorAction(int id) {
183 dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0));
186 public void performContextMenuAction(int id) {
187 dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0));
190 public void setComposingRegion(int start, int end) {
191 dispatchMessage(obtainMessageII(DO_SET_COMPOSING_REGION, start, end));
194 public void setComposingText(CharSequence text, int newCursorPosition) {
195 dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text));
198 public void finishComposingText() {
199 dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT));
202 public void sendKeyEvent(KeyEvent event) {
203 dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event));
206 public void clearMetaKeyStates(int states) {
207 dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0));
210 public void deleteSurroundingText(int beforeLength, int afterLength) {
211 dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
212 beforeLength, afterLength));
215 public void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
216 dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
217 beforeLength, afterLength));
220 public void beginBatchEdit() {
221 dispatchMessage(obtainMessage(DO_BEGIN_BATCH_EDIT));
224 public void endBatchEdit() {
225 dispatchMessage(obtainMessage(DO_END_BATCH_EDIT));
228 public void reportFullscreenMode(boolean enabled) {
229 dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0));
232 public void performPrivateCommand(String action, Bundle data) {
233 dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
236 public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
237 IInputContextCallback callback) {
238 dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
242 public void closeConnection() {
243 dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION));
246 public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts,
247 int seq, IInputContextCallback callback) {
248 dispatchMessage(obtainMessageIOOSC(DO_COMMIT_CONTENT, flags, inputContentInfo, opts, seq,
252 void dispatchMessage(Message msg) {
253 // If we are calling this from the main thread, then we can call
254 // right through. Otherwise, we need to send the message to the
256 if (Looper.myLooper() == mMainLooper) {
265 void executeMessage(Message msg) {
267 case DO_GET_TEXT_AFTER_CURSOR: {
268 SomeArgs args = (SomeArgs)msg.obj;
270 InputConnection ic = getInputConnection();
271 if (ic == null || !isActive()) {
272 Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
273 args.callback.setTextAfterCursor(null, args.seq);
276 args.callback.setTextAfterCursor(ic.getTextAfterCursor(
277 msg.arg1, msg.arg2), args.seq);
278 } catch (RemoteException e) {
279 Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
283 case DO_GET_TEXT_BEFORE_CURSOR: {
284 SomeArgs args = (SomeArgs)msg.obj;
286 InputConnection ic = getInputConnection();
287 if (ic == null || !isActive()) {
288 Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
289 args.callback.setTextBeforeCursor(null, args.seq);
292 args.callback.setTextBeforeCursor(ic.getTextBeforeCursor(
293 msg.arg1, msg.arg2), args.seq);
294 } catch (RemoteException e) {
295 Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
299 case DO_GET_SELECTED_TEXT: {
300 SomeArgs args = (SomeArgs)msg.obj;
302 InputConnection ic = getInputConnection();
303 if (ic == null || !isActive()) {
304 Log.w(TAG, "getSelectedText on inactive InputConnection");
305 args.callback.setSelectedText(null, args.seq);
308 args.callback.setSelectedText(ic.getSelectedText(
309 msg.arg1), args.seq);
310 } catch (RemoteException e) {
311 Log.w(TAG, "Got RemoteException calling setSelectedText", e);
315 case DO_GET_CURSOR_CAPS_MODE: {
316 SomeArgs args = (SomeArgs)msg.obj;
318 InputConnection ic = getInputConnection();
319 if (ic == null || !isActive()) {
320 Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
321 args.callback.setCursorCapsMode(0, args.seq);
324 args.callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
326 } catch (RemoteException e) {
327 Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
331 case DO_GET_EXTRACTED_TEXT: {
332 SomeArgs args = (SomeArgs)msg.obj;
334 InputConnection ic = getInputConnection();
335 if (ic == null || !isActive()) {
336 Log.w(TAG, "getExtractedText on inactive InputConnection");
337 args.callback.setExtractedText(null, args.seq);
340 args.callback.setExtractedText(ic.getExtractedText(
341 (ExtractedTextRequest)args.arg1, msg.arg1), args.seq);
342 } catch (RemoteException e) {
343 Log.w(TAG, "Got RemoteException calling setExtractedText", e);
347 case DO_COMMIT_TEXT: {
348 InputConnection ic = getInputConnection();
349 if (ic == null || !isActive()) {
350 Log.w(TAG, "commitText on inactive InputConnection");
353 ic.commitText((CharSequence)msg.obj, msg.arg1);
357 case DO_SET_SELECTION: {
358 InputConnection ic = getInputConnection();
359 if (ic == null || !isActive()) {
360 Log.w(TAG, "setSelection on inactive InputConnection");
363 ic.setSelection(msg.arg1, msg.arg2);
366 case DO_PERFORM_EDITOR_ACTION: {
367 InputConnection ic = getInputConnection();
368 if (ic == null || !isActive()) {
369 Log.w(TAG, "performEditorAction on inactive InputConnection");
372 ic.performEditorAction(msg.arg1);
375 case DO_PERFORM_CONTEXT_MENU_ACTION: {
376 InputConnection ic = getInputConnection();
377 if (ic == null || !isActive()) {
378 Log.w(TAG, "performContextMenuAction on inactive InputConnection");
381 ic.performContextMenuAction(msg.arg1);
384 case DO_COMMIT_COMPLETION: {
385 InputConnection ic = getInputConnection();
386 if (ic == null || !isActive()) {
387 Log.w(TAG, "commitCompletion on inactive InputConnection");
390 ic.commitCompletion((CompletionInfo)msg.obj);
393 case DO_COMMIT_CORRECTION: {
394 InputConnection ic = getInputConnection();
395 if (ic == null || !isActive()) {
396 Log.w(TAG, "commitCorrection on inactive InputConnection");
399 ic.commitCorrection((CorrectionInfo)msg.obj);
402 case DO_SET_COMPOSING_TEXT: {
403 InputConnection ic = getInputConnection();
404 if (ic == null || !isActive()) {
405 Log.w(TAG, "setComposingText on inactive InputConnection");
408 ic.setComposingText((CharSequence)msg.obj, msg.arg1);
412 case DO_SET_COMPOSING_REGION: {
413 InputConnection ic = getInputConnection();
414 if (ic == null || !isActive()) {
415 Log.w(TAG, "setComposingRegion on inactive InputConnection");
418 ic.setComposingRegion(msg.arg1, msg.arg2);
421 case DO_FINISH_COMPOSING_TEXT: {
422 InputConnection ic = getInputConnection();
423 // Note we do NOT check isActive() here, because this is safe
424 // for an IME to call at any time, and we need to allow it
425 // through to clean up our state after the IME has switched to
428 Log.w(TAG, "finishComposingText on inactive InputConnection");
431 ic.finishComposingText();
434 case DO_SEND_KEY_EVENT: {
435 InputConnection ic = getInputConnection();
436 if (ic == null || !isActive()) {
437 Log.w(TAG, "sendKeyEvent on inactive InputConnection");
440 ic.sendKeyEvent((KeyEvent)msg.obj);
444 case DO_CLEAR_META_KEY_STATES: {
445 InputConnection ic = getInputConnection();
446 if (ic == null || !isActive()) {
447 Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
450 ic.clearMetaKeyStates(msg.arg1);
453 case DO_DELETE_SURROUNDING_TEXT: {
454 InputConnection ic = getInputConnection();
455 if (ic == null || !isActive()) {
456 Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
459 ic.deleteSurroundingText(msg.arg1, msg.arg2);
462 case DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: {
463 InputConnection ic = getInputConnection();
464 if (ic == null || !isActive()) {
465 Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection");
468 ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2);
471 case DO_BEGIN_BATCH_EDIT: {
472 InputConnection ic = getInputConnection();
473 if (ic == null || !isActive()) {
474 Log.w(TAG, "beginBatchEdit on inactive InputConnection");
480 case DO_END_BATCH_EDIT: {
481 InputConnection ic = getInputConnection();
482 if (ic == null || !isActive()) {
483 Log.w(TAG, "endBatchEdit on inactive InputConnection");
489 case DO_REPORT_FULLSCREEN_MODE: {
490 InputConnection ic = getInputConnection();
491 boolean isBackground = false;
492 if (ic == null || !isActive()) {
493 Log.w(TAG, "reportFullscreenMode on inexistent InputConnection");
496 final boolean enabled = msg.arg1 == 1;
498 ic.reportFullscreenMode(enabled);
500 // Due to the nature of asynchronous event handling, currently InputMethodService
501 // has relied on the fact that #reportFullscreenMode() can be handled even when the
502 // InputConnection is inactive. We have to notify this event to InputMethodManager.
503 onReportFullscreenMode(enabled, isBackground);
506 case DO_PERFORM_PRIVATE_COMMAND: {
507 InputConnection ic = getInputConnection();
508 if (ic == null || !isActive()) {
509 Log.w(TAG, "performPrivateCommand on inactive InputConnection");
512 SomeArgs args = (SomeArgs)msg.obj;
513 ic.performPrivateCommand((String)args.arg1,
517 case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: {
518 SomeArgs args = (SomeArgs)msg.obj;
520 InputConnection ic = getInputConnection();
521 if (ic == null || !isActive()) {
522 Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
523 args.callback.setRequestUpdateCursorAnchorInfoResult(false, args.seq);
526 args.callback.setRequestUpdateCursorAnchorInfoResult(
527 ic.requestCursorUpdates(msg.arg1), args.seq);
528 } catch (RemoteException e) {
529 Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
533 case DO_CLOSE_CONNECTION: {
534 // Note that we do not need to worry about race condition here, because 1) mFinished
535 // is updated only inside this block, and 2) the code here is running on a Handler
536 // hence we assume multiple DO_CLOSE_CONNECTION messages will not be handled at the
542 InputConnection ic = getInputConnection();
543 // Note we do NOT check isActive() here, because this is safe
544 // for an IME to call at any time, and we need to allow it
545 // through to clean up our state after the IME has switched to
551 final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
552 if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
553 ic.closeConnection();
556 synchronized (mLock) {
557 mInputConnection = null;
563 case DO_COMMIT_CONTENT: {
564 final int flags = msg.arg1;
565 final boolean grantUriPermission =
566 (flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0;
567 SomeArgs args = (SomeArgs) msg.obj;
569 InputConnection ic = getInputConnection();
570 if (ic == null || !isActive()) {
571 Log.w(TAG, "commitContent on inactive InputConnection");
572 args.callback.setCommitContentResult(false, args.seq);
575 final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
576 if (inputContentInfo == null || !inputContentInfo.validate()) {
577 Log.w(TAG, "commitContent with invalid inputContentInfo="
579 args.callback.setCommitContentResult(false, args.seq);
582 if (grantUriPermission) {
583 inputContentInfo.requestPermission();
585 final boolean result =
586 ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
587 // If this request is not handled, then there is no reason to keep the URI
589 if (grantUriPermission && !result) {
590 inputContentInfo.releasePermission();
592 args.callback.setCommitContentResult(result, args.seq);
593 } catch (RemoteException e) {
594 Log.w(TAG, "Got RemoteException calling commitContent", e);
599 Log.w(TAG, "Unhandled message code: " + msg.what);
602 Message obtainMessage(int what) {
603 return mH.obtainMessage(what);
606 Message obtainMessageII(int what, int arg1, int arg2) {
607 return mH.obtainMessage(what, arg1, arg2);
610 Message obtainMessageO(int what, Object arg1) {
611 return mH.obtainMessage(what, 0, 0, arg1);
614 Message obtainMessageISC(int what, int arg1, int seq, IInputContextCallback callback) {
615 SomeArgs args = new SomeArgs();
616 args.callback = callback;
618 return mH.obtainMessage(what, arg1, 0, args);
621 Message obtainMessageIISC(int what, int arg1, int arg2, int seq, IInputContextCallback callback) {
622 SomeArgs args = new SomeArgs();
623 args.callback = callback;
625 return mH.obtainMessage(what, arg1, arg2, args);
628 Message obtainMessageIOOSC(int what, int arg1, Object objArg1, Object objArg2, int seq,
629 IInputContextCallback callback) {
630 SomeArgs args = new SomeArgs();
633 args.callback = callback;
635 return mH.obtainMessage(what, arg1, 0, args);
638 Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
639 IInputContextCallback callback) {
640 SomeArgs args = new SomeArgs();
642 args.callback = callback;
644 return mH.obtainMessage(what, arg1, 0, args);
647 Message obtainMessageIO(int what, int arg1, Object arg2) {
648 return mH.obtainMessage(what, arg1, 0, arg2);
651 Message obtainMessageOO(int what, Object arg1, Object arg2) {
652 SomeArgs args = new SomeArgs();
655 return mH.obtainMessage(what, 0, 0, args);