1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package com.android.internal.view; 18 19import com.android.internal.annotations.GuardedBy; 20 21import android.annotation.NonNull; 22import android.annotation.Nullable; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Looper; 26import android.os.Message; 27import android.os.RemoteException; 28import android.util.Log; 29import android.view.KeyEvent; 30import android.view.inputmethod.CompletionInfo; 31import android.view.inputmethod.CorrectionInfo; 32import android.view.inputmethod.ExtractedTextRequest; 33import android.view.inputmethod.InputConnection; 34import android.view.inputmethod.InputConnectionInspector; 35import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags; 36 37public abstract class IInputConnectionWrapper extends IInputContext.Stub { 38 static final String TAG = "IInputConnectionWrapper"; 39 40 private static final int DO_GET_TEXT_AFTER_CURSOR = 10; 41 private static final int DO_GET_TEXT_BEFORE_CURSOR = 20; 42 private static final int DO_GET_SELECTED_TEXT = 25; 43 private static final int DO_GET_CURSOR_CAPS_MODE = 30; 44 private static final int DO_GET_EXTRACTED_TEXT = 40; 45 private static final int DO_COMMIT_TEXT = 50; 46 private static final int DO_COMMIT_COMPLETION = 55; 47 private static final int DO_COMMIT_CORRECTION = 56; 48 private static final int DO_SET_SELECTION = 57; 49 private static final int DO_PERFORM_EDITOR_ACTION = 58; 50 private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59; 51 private static final int DO_SET_COMPOSING_TEXT = 60; 52 private static final int DO_SET_COMPOSING_REGION = 63; 53 private static final int DO_FINISH_COMPOSING_TEXT = 65; 54 private static final int DO_SEND_KEY_EVENT = 70; 55 private static final int DO_DELETE_SURROUNDING_TEXT = 80; 56 private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81; 57 private static final int DO_BEGIN_BATCH_EDIT = 90; 58 private static final int DO_END_BATCH_EDIT = 95; 59 private static final int DO_REPORT_FULLSCREEN_MODE = 100; 60 private static final int DO_PERFORM_PRIVATE_COMMAND = 120; 61 private static final int DO_CLEAR_META_KEY_STATES = 130; 62 private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140; 63 private static final int DO_CLOSE_CONNECTION = 150; 64 65 @GuardedBy("mLock") 66 @Nullable 67 private InputConnection mInputConnection; 68 69 private Looper mMainLooper; 70 private Handler mH; 71 private Object mLock = new Object(); 72 @GuardedBy("mLock") 73 private boolean mFinished = false; 74 @GuardedBy("mLock") 75 private String mInputMethodId; 76 77 static class SomeArgs { 78 Object arg1; 79 Object arg2; 80 IInputContextCallback callback; 81 int seq; 82 } 83 84 class MyHandler extends Handler { 85 MyHandler(Looper looper) { 86 super(looper); 87 } 88 89 @Override 90 public void handleMessage(Message msg) { 91 executeMessage(msg); 92 } 93 } 94 95 public IInputConnectionWrapper(Looper mainLooper, @NonNull InputConnection inputConnection) { 96 mInputConnection = inputConnection; 97 mMainLooper = mainLooper; 98 mH = new MyHandler(mMainLooper); 99 } 100 101 @Nullable 102 public InputConnection getInputConnection() { 103 synchronized (mLock) { 104 return mInputConnection; 105 } 106 } 107 108 protected boolean isFinished() { 109 synchronized (mLock) { 110 return mFinished; 111 } 112 } 113 114 public String getInputMethodId() { 115 synchronized (mLock) { 116 return mInputMethodId; 117 } 118 } 119 120 public void setInputMethodId(final String inputMethodId) { 121 synchronized (mLock) { 122 mInputMethodId = inputMethodId; 123 } 124 } 125 126 abstract protected boolean isActive(); 127 128 /** 129 * Called when the user took some actions that should be taken into consideration to update the 130 * LRU list for input method rotation. 131 */ 132 abstract protected void onUserAction(); 133 134 /** 135 * Called when the input method started or stopped full-screen mode. 136 * @param enabled {@code true} if the input method starts full-screen mode. 137 * @param calledInBackground {@code true} if this input connection is in a state when incoming 138 * events are usually ignored. 139 */ 140 abstract protected void onReportFullscreenMode(boolean enabled, boolean calledInBackground); 141 142 public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) { 143 dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback)); 144 } 145 146 public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) { 147 dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback)); 148 } 149 150 public void getSelectedText(int flags, int seq, IInputContextCallback callback) { 151 dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback)); 152 } 153 154 public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) { 155 dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback)); 156 } 157 158 public void getExtractedText(ExtractedTextRequest request, 159 int flags, int seq, IInputContextCallback callback) { 160 dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags, 161 request, seq, callback)); 162 } 163 164 public void commitText(CharSequence text, int newCursorPosition) { 165 dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text)); 166 } 167 168 public void commitCompletion(CompletionInfo text) { 169 dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text)); 170 } 171 172 public void commitCorrection(CorrectionInfo info) { 173 dispatchMessage(obtainMessageO(DO_COMMIT_CORRECTION, info)); 174 } 175 176 public void setSelection(int start, int end) { 177 dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end)); 178 } 179 180 public void performEditorAction(int id) { 181 dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0)); 182 } 183 184 public void performContextMenuAction(int id) { 185 dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0)); 186 } 187 188 public void setComposingRegion(int start, int end) { 189 dispatchMessage(obtainMessageII(DO_SET_COMPOSING_REGION, start, end)); 190 } 191 192 public void setComposingText(CharSequence text, int newCursorPosition) { 193 dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text)); 194 } 195 196 public void finishComposingText() { 197 dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT)); 198 } 199 200 public void sendKeyEvent(KeyEvent event) { 201 dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event)); 202 } 203 204 public void clearMetaKeyStates(int states) { 205 dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0)); 206 } 207 208 public void deleteSurroundingText(int beforeLength, int afterLength) { 209 dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT, 210 beforeLength, afterLength)); 211 } 212 213 public void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { 214 dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS, 215 beforeLength, afterLength)); 216 } 217 218 public void beginBatchEdit() { 219 dispatchMessage(obtainMessage(DO_BEGIN_BATCH_EDIT)); 220 } 221 222 public void endBatchEdit() { 223 dispatchMessage(obtainMessage(DO_END_BATCH_EDIT)); 224 } 225 226 public void reportFullscreenMode(boolean enabled) { 227 dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0)); 228 } 229 230 public void performPrivateCommand(String action, Bundle data) { 231 dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data)); 232 } 233 234 public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq, 235 IInputContextCallback callback) { 236 dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode, 237 seq, callback)); 238 } 239 240 public void closeConnection() { 241 dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION)); 242 } 243 244 void dispatchMessage(Message msg) { 245 // If we are calling this from the main thread, then we can call 246 // right through. Otherwise, we need to send the message to the 247 // main thread. 248 if (Looper.myLooper() == mMainLooper) { 249 executeMessage(msg); 250 msg.recycle(); 251 return; 252 } 253 254 mH.sendMessage(msg); 255 } 256 257 void executeMessage(Message msg) { 258 switch (msg.what) { 259 case DO_GET_TEXT_AFTER_CURSOR: { 260 SomeArgs args = (SomeArgs)msg.obj; 261 try { 262 InputConnection ic = getInputConnection(); 263 if (ic == null || !isActive()) { 264 Log.w(TAG, "getTextAfterCursor on inactive InputConnection"); 265 args.callback.setTextAfterCursor(null, args.seq); 266 return; 267 } 268 args.callback.setTextAfterCursor(ic.getTextAfterCursor( 269 msg.arg1, msg.arg2), args.seq); 270 } catch (RemoteException e) { 271 Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e); 272 } 273 return; 274 } 275 case DO_GET_TEXT_BEFORE_CURSOR: { 276 SomeArgs args = (SomeArgs)msg.obj; 277 try { 278 InputConnection ic = getInputConnection(); 279 if (ic == null || !isActive()) { 280 Log.w(TAG, "getTextBeforeCursor on inactive InputConnection"); 281 args.callback.setTextBeforeCursor(null, args.seq); 282 return; 283 } 284 args.callback.setTextBeforeCursor(ic.getTextBeforeCursor( 285 msg.arg1, msg.arg2), args.seq); 286 } catch (RemoteException e) { 287 Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e); 288 } 289 return; 290 } 291 case DO_GET_SELECTED_TEXT: { 292 SomeArgs args = (SomeArgs)msg.obj; 293 try { 294 InputConnection ic = getInputConnection(); 295 if (ic == null || !isActive()) { 296 Log.w(TAG, "getSelectedText on inactive InputConnection"); 297 args.callback.setSelectedText(null, args.seq); 298 return; 299 } 300 args.callback.setSelectedText(ic.getSelectedText( 301 msg.arg1), args.seq); 302 } catch (RemoteException e) { 303 Log.w(TAG, "Got RemoteException calling setSelectedText", e); 304 } 305 return; 306 } 307 case DO_GET_CURSOR_CAPS_MODE: { 308 SomeArgs args = (SomeArgs)msg.obj; 309 try { 310 InputConnection ic = getInputConnection(); 311 if (ic == null || !isActive()) { 312 Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); 313 args.callback.setCursorCapsMode(0, args.seq); 314 return; 315 } 316 args.callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1), 317 args.seq); 318 } catch (RemoteException e) { 319 Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e); 320 } 321 return; 322 } 323 case DO_GET_EXTRACTED_TEXT: { 324 SomeArgs args = (SomeArgs)msg.obj; 325 try { 326 InputConnection ic = getInputConnection(); 327 if (ic == null || !isActive()) { 328 Log.w(TAG, "getExtractedText on inactive InputConnection"); 329 args.callback.setExtractedText(null, args.seq); 330 return; 331 } 332 args.callback.setExtractedText(ic.getExtractedText( 333 (ExtractedTextRequest)args.arg1, msg.arg1), args.seq); 334 } catch (RemoteException e) { 335 Log.w(TAG, "Got RemoteException calling setExtractedText", e); 336 } 337 return; 338 } 339 case DO_COMMIT_TEXT: { 340 InputConnection ic = getInputConnection(); 341 if (ic == null || !isActive()) { 342 Log.w(TAG, "commitText on inactive InputConnection"); 343 return; 344 } 345 ic.commitText((CharSequence)msg.obj, msg.arg1); 346 onUserAction(); 347 return; 348 } 349 case DO_SET_SELECTION: { 350 InputConnection ic = getInputConnection(); 351 if (ic == null || !isActive()) { 352 Log.w(TAG, "setSelection on inactive InputConnection"); 353 return; 354 } 355 ic.setSelection(msg.arg1, msg.arg2); 356 return; 357 } 358 case DO_PERFORM_EDITOR_ACTION: { 359 InputConnection ic = getInputConnection(); 360 if (ic == null || !isActive()) { 361 Log.w(TAG, "performEditorAction on inactive InputConnection"); 362 return; 363 } 364 ic.performEditorAction(msg.arg1); 365 return; 366 } 367 case DO_PERFORM_CONTEXT_MENU_ACTION: { 368 InputConnection ic = getInputConnection(); 369 if (ic == null || !isActive()) { 370 Log.w(TAG, "performContextMenuAction on inactive InputConnection"); 371 return; 372 } 373 ic.performContextMenuAction(msg.arg1); 374 return; 375 } 376 case DO_COMMIT_COMPLETION: { 377 InputConnection ic = getInputConnection(); 378 if (ic == null || !isActive()) { 379 Log.w(TAG, "commitCompletion on inactive InputConnection"); 380 return; 381 } 382 ic.commitCompletion((CompletionInfo)msg.obj); 383 return; 384 } 385 case DO_COMMIT_CORRECTION: { 386 InputConnection ic = getInputConnection(); 387 if (ic == null || !isActive()) { 388 Log.w(TAG, "commitCorrection on inactive InputConnection"); 389 return; 390 } 391 ic.commitCorrection((CorrectionInfo)msg.obj); 392 return; 393 } 394 case DO_SET_COMPOSING_TEXT: { 395 InputConnection ic = getInputConnection(); 396 if (ic == null || !isActive()) { 397 Log.w(TAG, "setComposingText on inactive InputConnection"); 398 return; 399 } 400 ic.setComposingText((CharSequence)msg.obj, msg.arg1); 401 onUserAction(); 402 return; 403 } 404 case DO_SET_COMPOSING_REGION: { 405 InputConnection ic = getInputConnection(); 406 if (ic == null || !isActive()) { 407 Log.w(TAG, "setComposingRegion on inactive InputConnection"); 408 return; 409 } 410 ic.setComposingRegion(msg.arg1, msg.arg2); 411 return; 412 } 413 case DO_FINISH_COMPOSING_TEXT: { 414 InputConnection ic = getInputConnection(); 415 // Note we do NOT check isActive() here, because this is safe 416 // for an IME to call at any time, and we need to allow it 417 // through to clean up our state after the IME has switched to 418 // another client. 419 if (ic == null) { 420 Log.w(TAG, "finishComposingText on inactive InputConnection"); 421 return; 422 } 423 ic.finishComposingText(); 424 return; 425 } 426 case DO_SEND_KEY_EVENT: { 427 InputConnection ic = getInputConnection(); 428 if (ic == null || !isActive()) { 429 Log.w(TAG, "sendKeyEvent on inactive InputConnection"); 430 return; 431 } 432 ic.sendKeyEvent((KeyEvent)msg.obj); 433 onUserAction(); 434 return; 435 } 436 case DO_CLEAR_META_KEY_STATES: { 437 InputConnection ic = getInputConnection(); 438 if (ic == null || !isActive()) { 439 Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); 440 return; 441 } 442 ic.clearMetaKeyStates(msg.arg1); 443 return; 444 } 445 case DO_DELETE_SURROUNDING_TEXT: { 446 InputConnection ic = getInputConnection(); 447 if (ic == null || !isActive()) { 448 Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); 449 return; 450 } 451 ic.deleteSurroundingText(msg.arg1, msg.arg2); 452 return; 453 } 454 case DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: { 455 InputConnection ic = getInputConnection(); 456 if (ic == null || !isActive()) { 457 Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection"); 458 return; 459 } 460 ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2); 461 return; 462 } 463 case DO_BEGIN_BATCH_EDIT: { 464 InputConnection ic = getInputConnection(); 465 if (ic == null || !isActive()) { 466 Log.w(TAG, "beginBatchEdit on inactive InputConnection"); 467 return; 468 } 469 ic.beginBatchEdit(); 470 return; 471 } 472 case DO_END_BATCH_EDIT: { 473 InputConnection ic = getInputConnection(); 474 if (ic == null || !isActive()) { 475 Log.w(TAG, "endBatchEdit on inactive InputConnection"); 476 return; 477 } 478 ic.endBatchEdit(); 479 return; 480 } 481 case DO_REPORT_FULLSCREEN_MODE: { 482 InputConnection ic = getInputConnection(); 483 boolean isBackground = false; 484 if (ic == null || !isActive()) { 485 Log.w(TAG, "reportFullscreenMode on inexistent InputConnection"); 486 isBackground = true; 487 } 488 final boolean enabled = msg.arg1 == 1; 489 if (!isBackground) { 490 ic.reportFullscreenMode(enabled); 491 } 492 // Due to the nature of asynchronous event handling, currently InputMethodService 493 // has relied on the fact that #reportFullscreenMode() can be handled even when the 494 // InputConnection is inactive. We have to notify this event to InputMethodManager. 495 onReportFullscreenMode(enabled, isBackground); 496 return; 497 } 498 case DO_PERFORM_PRIVATE_COMMAND: { 499 InputConnection ic = getInputConnection(); 500 if (ic == null || !isActive()) { 501 Log.w(TAG, "performPrivateCommand on inactive InputConnection"); 502 return; 503 } 504 SomeArgs args = (SomeArgs)msg.obj; 505 ic.performPrivateCommand((String)args.arg1, 506 (Bundle)args.arg2); 507 return; 508 } 509 case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: { 510 SomeArgs args = (SomeArgs)msg.obj; 511 try { 512 InputConnection ic = getInputConnection(); 513 if (ic == null || !isActive()) { 514 Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); 515 args.callback.setRequestUpdateCursorAnchorInfoResult(false, args.seq); 516 return; 517 } 518 args.callback.setRequestUpdateCursorAnchorInfoResult( 519 ic.requestCursorUpdates(msg.arg1), args.seq); 520 } catch (RemoteException e) { 521 Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e); 522 } 523 return; 524 } 525 case DO_CLOSE_CONNECTION: { 526 // Note that we do not need to worry about race condition here, because 1) mFinished 527 // is updated only inside this block, and 2) the code here is running on a Handler 528 // hence we assume multiple DO_CLOSE_CONNECTION messages will not be handled at the 529 // same time. 530 if (isFinished()) { 531 return; 532 } 533 try { 534 InputConnection ic = getInputConnection(); 535 // Note we do NOT check isActive() here, because this is safe 536 // for an IME to call at any time, and we need to allow it 537 // through to clean up our state after the IME has switched to 538 // another client. 539 if (ic == null) { 540 return; 541 } 542 @MissingMethodFlags 543 final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic); 544 if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) { 545 ic.closeConnection(); 546 } 547 } finally { 548 synchronized (mLock) { 549 mInputConnection = null; 550 mFinished = true; 551 } 552 } 553 return; 554 } 555 } 556 Log.w(TAG, "Unhandled message code: " + msg.what); 557 } 558 559 Message obtainMessage(int what) { 560 return mH.obtainMessage(what); 561 } 562 563 Message obtainMessageII(int what, int arg1, int arg2) { 564 return mH.obtainMessage(what, arg1, arg2); 565 } 566 567 Message obtainMessageO(int what, Object arg1) { 568 return mH.obtainMessage(what, 0, 0, arg1); 569 } 570 571 Message obtainMessageISC(int what, int arg1, int seq, IInputContextCallback callback) { 572 SomeArgs args = new SomeArgs(); 573 args.callback = callback; 574 args.seq = seq; 575 return mH.obtainMessage(what, arg1, 0, args); 576 } 577 578 Message obtainMessageIISC(int what, int arg1, int arg2, int seq, IInputContextCallback callback) { 579 SomeArgs args = new SomeArgs(); 580 args.callback = callback; 581 args.seq = seq; 582 return mH.obtainMessage(what, arg1, arg2, args); 583 } 584 585 Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) { 586 SomeArgs args = new SomeArgs(); 587 args.arg1 = arg1; 588 args.callback = callback; 589 args.seq = seq; 590 return mH.obtainMessage(what, 0, 0, args); 591 } 592 593 Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq, 594 IInputContextCallback callback) { 595 SomeArgs args = new SomeArgs(); 596 args.arg1 = arg2; 597 args.callback = callback; 598 args.seq = seq; 599 return mH.obtainMessage(what, arg1, 0, args); 600 } 601 602 Message obtainMessageIO(int what, int arg1, Object arg2) { 603 return mH.obtainMessage(what, arg1, 0, arg2); 604 } 605 606 Message obtainMessageOO(int what, Object arg1, Object arg2) { 607 SomeArgs args = new SomeArgs(); 608 args.arg1 = arg1; 609 args.arg2 = arg2; 610 return mH.obtainMessage(what, 0, 0, args); 611 } 612} 613