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