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 17 package com.android.internal.view; 18 19 import android.annotation.AnyThread; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.inputmethodservice.AbstractInputMethodService; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.RemoteException; 27 import android.util.imetracing.ImeTracing; 28 import android.util.imetracing.InputConnectionHelper; 29 import android.util.proto.ProtoOutputStream; 30 import android.view.KeyEvent; 31 import android.view.inputmethod.CompletionInfo; 32 import android.view.inputmethod.CorrectionInfo; 33 import android.view.inputmethod.ExtractedText; 34 import android.view.inputmethod.ExtractedTextRequest; 35 import android.view.inputmethod.InputConnection; 36 import android.view.inputmethod.InputConnectionInspector; 37 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags; 38 import android.view.inputmethod.InputContentInfo; 39 import android.view.inputmethod.SurroundingText; 40 41 import com.android.internal.inputmethod.CancellationGroup; 42 import com.android.internal.inputmethod.Completable; 43 import com.android.internal.inputmethod.ResultCallbacks; 44 45 import java.lang.ref.WeakReference; 46 47 public class InputConnectionWrapper implements InputConnection { 48 private static final String TAG = "InputConnectionWrapper"; 49 50 private static final int MAX_WAIT_TIME_MILLIS = 2000; 51 private final IInputContext mIInputContext; 52 @NonNull 53 private final WeakReference<AbstractInputMethodService> mInputMethodService; 54 55 @MissingMethodFlags 56 private final int mMissingMethods; 57 58 /** 59 * Signaled when the system decided to take away IME focus from the target app. 60 * 61 * <p>This is expected to be signaled immediately when the IME process receives 62 * {@link IInputMethod#unbindInput()}.</p> 63 */ 64 @NonNull 65 private final CancellationGroup mCancellationGroup; 66 InputConnectionWrapper( @onNull WeakReference<AbstractInputMethodService> inputMethodService, IInputContext inputContext, @MissingMethodFlags int missingMethods, @NonNull CancellationGroup cancellationGroup)67 public InputConnectionWrapper( 68 @NonNull WeakReference<AbstractInputMethodService> inputMethodService, 69 IInputContext inputContext, @MissingMethodFlags int missingMethods, 70 @NonNull CancellationGroup cancellationGroup) { 71 mInputMethodService = inputMethodService; 72 mIInputContext = inputContext; 73 mMissingMethods = missingMethods; 74 mCancellationGroup = cancellationGroup; 75 } 76 77 /** 78 * See {@link InputConnection#getTextAfterCursor(int, int)}. 79 */ 80 @Nullable 81 @AnyThread getTextAfterCursor(@ntRangefrom = 0) int length, int flags)82 public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) { 83 if (length < 0 || mCancellationGroup.isCanceled()) { 84 return null; 85 } 86 87 final Completable.CharSequence value = Completable.createCharSequence(); 88 try { 89 mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value)); 90 } catch (RemoteException e) { 91 return null; 92 } 93 CharSequence result = Completable.getResultOrNull( 94 value, TAG, "getTextAfterCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); 95 96 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 97 if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { 98 ProtoOutputStream icProto = InputConnectionHelper.buildGetTextAfterCursorProto(length, 99 flags, result); 100 ImeTracing.getInstance().triggerServiceDump(TAG + "#getTextAfterCursor", 101 inputMethodService, icProto); 102 } 103 104 return result; 105 } 106 107 /** 108 * See {@link InputConnection#getTextBeforeCursor(int, int)}. 109 */ 110 @Nullable 111 @AnyThread getTextBeforeCursor(@ntRangefrom = 0) int length, int flags)112 public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) { 113 if (length < 0 || mCancellationGroup.isCanceled()) { 114 return null; 115 } 116 117 final Completable.CharSequence value = Completable.createCharSequence(); 118 try { 119 mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value)); 120 } catch (RemoteException e) { 121 return null; 122 } 123 CharSequence result = Completable.getResultOrNull( 124 value, TAG, "getTextBeforeCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); 125 126 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 127 if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { 128 ProtoOutputStream icProto = InputConnectionHelper.buildGetTextBeforeCursorProto(length, 129 flags, result); 130 ImeTracing.getInstance().triggerServiceDump(TAG + "#getTextBeforeCursor", 131 inputMethodService, icProto); 132 } 133 134 return result; 135 } 136 137 @AnyThread getSelectedText(int flags)138 public CharSequence getSelectedText(int flags) { 139 if (mCancellationGroup.isCanceled()) { 140 return null; 141 } 142 143 if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) { 144 // This method is not implemented. 145 return null; 146 } 147 final Completable.CharSequence value = Completable.createCharSequence(); 148 try { 149 mIInputContext.getSelectedText(flags, ResultCallbacks.of(value)); 150 } catch (RemoteException e) { 151 return null; 152 } 153 CharSequence result = Completable.getResultOrNull( 154 value, TAG, "getSelectedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); 155 156 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 157 if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { 158 ProtoOutputStream icProto = InputConnectionHelper.buildGetSelectedTextProto(flags, 159 result); 160 ImeTracing.getInstance().triggerServiceDump(TAG + "#getSelectedText", 161 inputMethodService, icProto); 162 } 163 164 return result; 165 } 166 167 /** 168 * Get {@link SurroundingText} around the current cursor, with <var>beforeLength</var> 169 * characters of text before the cursor, <var>afterLength</var> characters of text after the 170 * cursor, and all of the selected text. 171 * @param beforeLength The expected length of the text before the cursor 172 * @param afterLength The expected length of the text after the cursor 173 * @param flags Supplies additional options controlling how the text is returned. May be either 174 * 0 or {@link #GET_TEXT_WITH_STYLES}. 175 * @return the surrounding text around the cursor position; the length of the returned text 176 * might be less than requested. It could also be {@code null} when the editor or system could 177 * not support this protocol. 178 */ 179 @AnyThread getSurroundingText( @ntRangefrom = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags)180 public SurroundingText getSurroundingText( 181 @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) { 182 if (beforeLength < 0 || afterLength < 0 || mCancellationGroup.isCanceled()) { 183 return null; 184 } 185 186 if (isMethodMissing(MissingMethodFlags.GET_SURROUNDING_TEXT)) { 187 // This method is not implemented. 188 return null; 189 } 190 final Completable.SurroundingText value = Completable.createSurroundingText(); 191 try { 192 mIInputContext.getSurroundingText(beforeLength, afterLength, flags, 193 ResultCallbacks.of(value)); 194 } catch (RemoteException e) { 195 return null; 196 } 197 SurroundingText result = Completable.getResultOrNull( 198 value, TAG, "getSurroundingText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); 199 200 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 201 if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { 202 ProtoOutputStream icProto = InputConnectionHelper.buildGetSurroundingTextProto( 203 beforeLength, afterLength, flags, result); 204 ImeTracing.getInstance().triggerServiceDump(TAG + "#getSurroundingText", 205 inputMethodService, icProto); 206 } 207 208 return result; 209 } 210 211 @AnyThread getCursorCapsMode(int reqModes)212 public int getCursorCapsMode(int reqModes) { 213 if (mCancellationGroup.isCanceled()) { 214 return 0; 215 } 216 217 final Completable.Int value = Completable.createInt(); 218 try { 219 mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value)); 220 } catch (RemoteException e) { 221 return 0; 222 } 223 int result = Completable.getResultOrZero( 224 value, TAG, "getCursorCapsMode()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); 225 226 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 227 if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { 228 ProtoOutputStream icProto = InputConnectionHelper.buildGetCursorCapsModeProto( 229 reqModes, result); 230 ImeTracing.getInstance().triggerServiceDump(TAG + "#getCursorCapsMode", 231 inputMethodService, icProto); 232 } 233 234 return result; 235 } 236 237 @AnyThread getExtractedText(ExtractedTextRequest request, int flags)238 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 239 if (mCancellationGroup.isCanceled()) { 240 return null; 241 } 242 243 final Completable.ExtractedText value = Completable.createExtractedText(); 244 try { 245 mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value)); 246 } catch (RemoteException e) { 247 return null; 248 } 249 ExtractedText result = Completable.getResultOrNull( 250 value, TAG, "getExtractedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); 251 252 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 253 if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { 254 ProtoOutputStream icProto = InputConnectionHelper.buildGetExtractedTextProto( 255 request, flags, result); 256 ImeTracing.getInstance().triggerServiceDump(TAG + "#getExtractedText", 257 inputMethodService, icProto); 258 } 259 260 return result; 261 } 262 263 @AnyThread commitText(CharSequence text, int newCursorPosition)264 public boolean commitText(CharSequence text, int newCursorPosition) { 265 try { 266 mIInputContext.commitText(text, newCursorPosition); 267 notifyUserActionIfNecessary(); 268 return true; 269 } catch (RemoteException e) { 270 return false; 271 } 272 } 273 274 @AnyThread notifyUserActionIfNecessary()275 private void notifyUserActionIfNecessary() { 276 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 277 if (inputMethodService == null) { 278 // This basically should not happen, because it's the the caller of this method. 279 return; 280 } 281 inputMethodService.notifyUserActionIfNecessary(); 282 } 283 284 @AnyThread commitCompletion(CompletionInfo text)285 public boolean commitCompletion(CompletionInfo text) { 286 if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) { 287 // This method is not implemented. 288 return false; 289 } 290 try { 291 mIInputContext.commitCompletion(text); 292 return true; 293 } catch (RemoteException e) { 294 return false; 295 } 296 } 297 298 @AnyThread commitCorrection(CorrectionInfo correctionInfo)299 public boolean commitCorrection(CorrectionInfo correctionInfo) { 300 try { 301 mIInputContext.commitCorrection(correctionInfo); 302 return true; 303 } catch (RemoteException e) { 304 return false; 305 } 306 } 307 308 @AnyThread setSelection(int start, int end)309 public boolean setSelection(int start, int end) { 310 try { 311 mIInputContext.setSelection(start, end); 312 return true; 313 } catch (RemoteException e) { 314 return false; 315 } 316 } 317 318 @AnyThread performEditorAction(int actionCode)319 public boolean performEditorAction(int actionCode) { 320 try { 321 mIInputContext.performEditorAction(actionCode); 322 return true; 323 } catch (RemoteException e) { 324 return false; 325 } 326 } 327 328 @AnyThread performContextMenuAction(int id)329 public boolean performContextMenuAction(int id) { 330 try { 331 mIInputContext.performContextMenuAction(id); 332 return true; 333 } catch (RemoteException e) { 334 return false; 335 } 336 } 337 338 @AnyThread setComposingRegion(int start, int end)339 public boolean setComposingRegion(int start, int end) { 340 if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) { 341 // This method is not implemented. 342 return false; 343 } 344 try { 345 mIInputContext.setComposingRegion(start, end); 346 return true; 347 } catch (RemoteException e) { 348 return false; 349 } 350 } 351 352 @AnyThread setComposingText(CharSequence text, int newCursorPosition)353 public boolean setComposingText(CharSequence text, int newCursorPosition) { 354 try { 355 mIInputContext.setComposingText(text, newCursorPosition); 356 notifyUserActionIfNecessary(); 357 return true; 358 } catch (RemoteException e) { 359 return false; 360 } 361 } 362 363 @AnyThread finishComposingText()364 public boolean finishComposingText() { 365 try { 366 mIInputContext.finishComposingText(); 367 return true; 368 } catch (RemoteException e) { 369 return false; 370 } 371 } 372 373 @AnyThread beginBatchEdit()374 public boolean beginBatchEdit() { 375 try { 376 mIInputContext.beginBatchEdit(); 377 return true; 378 } catch (RemoteException e) { 379 return false; 380 } 381 } 382 383 @AnyThread endBatchEdit()384 public boolean endBatchEdit() { 385 try { 386 mIInputContext.endBatchEdit(); 387 return true; 388 } catch (RemoteException e) { 389 return false; 390 } 391 } 392 393 @AnyThread sendKeyEvent(KeyEvent event)394 public boolean sendKeyEvent(KeyEvent event) { 395 try { 396 mIInputContext.sendKeyEvent(event); 397 notifyUserActionIfNecessary(); 398 return true; 399 } catch (RemoteException e) { 400 return false; 401 } 402 } 403 404 @AnyThread clearMetaKeyStates(int states)405 public boolean clearMetaKeyStates(int states) { 406 try { 407 mIInputContext.clearMetaKeyStates(states); 408 return true; 409 } catch (RemoteException e) { 410 return false; 411 } 412 } 413 414 @AnyThread deleteSurroundingText(int beforeLength, int afterLength)415 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 416 try { 417 mIInputContext.deleteSurroundingText(beforeLength, afterLength); 418 return true; 419 } catch (RemoteException e) { 420 return false; 421 } 422 } 423 424 @AnyThread deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)425 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { 426 if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) { 427 // This method is not implemented. 428 return false; 429 } 430 try { 431 mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength); 432 return true; 433 } catch (RemoteException e) { 434 return false; 435 } 436 } 437 438 @AnyThread reportFullscreenMode(boolean enabled)439 public boolean reportFullscreenMode(boolean enabled) { 440 // Nothing should happen when called from input method. 441 return false; 442 } 443 444 @AnyThread 445 @Override performSpellCheck()446 public boolean performSpellCheck() { 447 try { 448 mIInputContext.performSpellCheck(); 449 return true; 450 } catch (RemoteException e) { 451 return false; 452 } 453 } 454 455 @AnyThread performPrivateCommand(String action, Bundle data)456 public boolean performPrivateCommand(String action, Bundle data) { 457 try { 458 mIInputContext.performPrivateCommand(action, data); 459 return true; 460 } catch (RemoteException e) { 461 return false; 462 } 463 } 464 465 @AnyThread requestCursorUpdates(int cursorUpdateMode)466 public boolean requestCursorUpdates(int cursorUpdateMode) { 467 if (mCancellationGroup.isCanceled()) { 468 return false; 469 } 470 471 if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) { 472 // This method is not implemented. 473 return false; 474 } 475 final Completable.Int value = Completable.createInt(); 476 try { 477 mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, 478 ResultCallbacks.of(value)); 479 } catch (RemoteException e) { 480 return false; 481 } 482 return Completable.getResultOrZero(value, TAG, "requestUpdateCursorAnchorInfo()", 483 mCancellationGroup, MAX_WAIT_TIME_MILLIS) != 0; 484 } 485 486 @AnyThread getHandler()487 public Handler getHandler() { 488 // Nothing should happen when called from input method. 489 return null; 490 } 491 492 @AnyThread closeConnection()493 public void closeConnection() { 494 // Nothing should happen when called from input method. 495 } 496 497 @AnyThread commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)498 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { 499 if (mCancellationGroup.isCanceled()) { 500 return false; 501 } 502 503 if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) { 504 // This method is not implemented. 505 return false; 506 } 507 508 if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { 509 final AbstractInputMethodService inputMethodService = mInputMethodService.get(); 510 if (inputMethodService == null) { 511 // This basically should not happen, because it's the caller of this method. 512 return false; 513 } 514 inputMethodService.exposeContent(inputContentInfo, this); 515 } 516 517 final Completable.Int value = Completable.createInt(); 518 try { 519 mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value)); 520 } catch (RemoteException e) { 521 return false; 522 } 523 return Completable.getResultOrZero( 524 value, TAG, "commitContent()", mCancellationGroup, MAX_WAIT_TIME_MILLIS) != 0; 525 } 526 527 /** 528 * See {@link InputConnection#setImeConsumesInput(boolean)}. 529 */ 530 @AnyThread setImeConsumesInput(boolean imeConsumesInput)531 public boolean setImeConsumesInput(boolean imeConsumesInput) { 532 try { 533 mIInputContext.setImeConsumesInput(imeConsumesInput); 534 return true; 535 } catch (RemoteException e) { 536 return false; 537 } 538 } 539 540 @AnyThread isMethodMissing(@issingMethodFlags final int methodFlag)541 private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) { 542 return (mMissingMethods & methodFlag) == methodFlag; 543 } 544 545 @AnyThread 546 @Override toString()547 public String toString() { 548 return "InputConnectionWrapper{idHash=#" 549 + Integer.toHexString(System.identityHashCode(this)) 550 + " mMissingMethods=" 551 + InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}"; 552 } 553 } 554