1 /* 2 * Copyright (C) 2021 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 static android.view.inputmethod.TextBoundsInfoResult.CODE_CANCELLED; 20 21 import android.annotation.AnyThread; 22 import android.annotation.CallbackExecutor; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.graphics.RectF; 26 import android.os.Bundle; 27 import android.os.CancellationSignal; 28 import android.os.CancellationSignalBeamer; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.os.ResultReceiver; 32 import android.view.KeyEvent; 33 import android.view.inputmethod.CompletionInfo; 34 import android.view.inputmethod.CorrectionInfo; 35 import android.view.inputmethod.ExtractedText; 36 import android.view.inputmethod.ExtractedTextRequest; 37 import android.view.inputmethod.HandwritingGesture; 38 import android.view.inputmethod.InputConnection; 39 import android.view.inputmethod.InputContentInfo; 40 import android.view.inputmethod.ParcelableHandwritingGesture; 41 import android.view.inputmethod.SurroundingText; 42 import android.view.inputmethod.TextAttribute; 43 import android.view.inputmethod.TextBoundsInfo; 44 import android.view.inputmethod.TextBoundsInfoResult; 45 46 import com.android.internal.infra.AndroidFuture; 47 import com.android.internal.inputmethod.IRemoteInputConnection; 48 import com.android.internal.inputmethod.InputConnectionCommandHeader; 49 50 import java.util.Objects; 51 import java.util.concurrent.Executor; 52 import java.util.function.Consumer; 53 import java.util.function.IntConsumer; 54 55 /** 56 * A stateless wrapper of {@link com.android.internal.inputmethod.IRemoteInputConnection} to 57 * encapsulate boilerplate code around {@link AndroidFuture} and {@link RemoteException}. 58 */ 59 final class IRemoteInputConnectionInvoker { 60 61 @NonNull 62 private final IRemoteInputConnection mConnection; 63 private final int mSessionId; 64 private CancellationSignalBeamer.Sender mBeamer; 65 IRemoteInputConnectionInvoker(@onNull IRemoteInputConnection inputConnection, int sessionId)66 private IRemoteInputConnectionInvoker(@NonNull IRemoteInputConnection inputConnection, 67 int sessionId) { 68 mConnection = inputConnection; 69 mSessionId = sessionId; 70 } 71 72 private abstract static class OnceResultReceiver<C> extends ResultReceiver { 73 @Nullable 74 private C mConsumer; 75 @Nullable 76 private Executor mExecutor; 77 OnceResultReceiver(@onNull Executor executor, @NonNull C consumer)78 protected OnceResultReceiver(@NonNull Executor executor, @NonNull C consumer) { 79 super(null); 80 Objects.requireNonNull(executor); 81 Objects.requireNonNull(consumer); 82 mExecutor = executor; 83 mConsumer = consumer; 84 } 85 86 @Override onReceiveResult(int resultCode, Bundle resultData)87 protected final void onReceiveResult(int resultCode, Bundle resultData) { 88 final Executor executor; 89 final C consumer; 90 synchronized (this) { 91 executor = mExecutor; 92 consumer = mConsumer; 93 mExecutor = null; 94 mConsumer = null; 95 } 96 if (executor != null && consumer != null) { 97 dispatch(executor, consumer, resultCode, resultData); 98 } 99 } 100 dispatch(@onNull Executor executor, @NonNull C consumer, int code, Bundle data)101 protected abstract void dispatch(@NonNull Executor executor, @NonNull C consumer, int code, 102 Bundle data); 103 } 104 105 /** 106 * Subclass of {@link ResultReceiver} used by 107 * {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)} for providing 108 * callback. 109 */ 110 private static final class IntResultReceiver extends OnceResultReceiver<IntConsumer> { IntResultReceiver(@onNull Executor executor, @NonNull IntConsumer consumer)111 IntResultReceiver(@NonNull Executor executor, @NonNull IntConsumer consumer) { 112 super(executor, consumer); 113 } 114 115 @Override dispatch(@onNull Executor executor, @NonNull IntConsumer consumer, int code, Bundle data)116 protected void dispatch(@NonNull Executor executor, @NonNull IntConsumer consumer, int code, 117 Bundle data) { 118 executor.execute(() -> consumer.accept(code)); 119 } 120 } 121 122 /** 123 * Subclass of {@link ResultReceiver} used by 124 * {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing 125 * callback. 126 */ 127 private static final class TextBoundsInfoResultReceiver extends 128 OnceResultReceiver<Consumer<TextBoundsInfoResult>> { TextBoundsInfoResultReceiver(@onNull Executor executor, @NonNull Consumer<TextBoundsInfoResult> consumer)129 TextBoundsInfoResultReceiver(@NonNull Executor executor, 130 @NonNull Consumer<TextBoundsInfoResult> consumer) { 131 super(executor, consumer); 132 } 133 134 @Override dispatch(@onNull Executor executor, @NonNull Consumer<TextBoundsInfoResult> consumer, int code, Bundle data)135 protected void dispatch(@NonNull Executor executor, 136 @NonNull Consumer<TextBoundsInfoResult> consumer, int code, Bundle data) { 137 final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult( 138 code, TextBoundsInfo.createFromBundle(data)); 139 executor.execute(() -> consumer.accept(textBoundsInfoResult)); 140 } 141 } 142 143 /** 144 * Creates a new instance of {@link IRemoteInputConnectionInvoker} for the given 145 * {@link IRemoteInputConnection}. 146 * 147 * @param connection {@link IRemoteInputConnection} to be wrapped. 148 * @return A new instance of {@link IRemoteInputConnectionInvoker}. 149 */ create(@onNull IRemoteInputConnection connection)150 public static IRemoteInputConnectionInvoker create(@NonNull IRemoteInputConnection connection) { 151 Objects.requireNonNull(connection); 152 return new IRemoteInputConnectionInvoker(connection, 0); 153 } 154 155 /** 156 * Creates a new instance of {@link IRemoteInputConnectionInvoker} with the given 157 * {@code sessionId}. 158 * 159 * @param sessionId the new session ID to be used. 160 * @return A new instance of {@link IRemoteInputConnectionInvoker}. 161 */ 162 @NonNull cloneWithSessionId(int sessionId)163 public IRemoteInputConnectionInvoker cloneWithSessionId(int sessionId) { 164 return new IRemoteInputConnectionInvoker(mConnection, sessionId); 165 } 166 167 /** 168 * @param connection {@code IRemoteInputConnection} to be compared with 169 * @return {@code true} if the underlying {@code IRemoteInputConnection} is the same. 170 * {@code false} if {@code inputContext} is {@code null}. 171 */ 172 @AnyThread isSameConnection(@onNull IRemoteInputConnection connection)173 public boolean isSameConnection(@NonNull IRemoteInputConnection connection) { 174 if (connection == null) { 175 return false; 176 } 177 return mConnection.asBinder() == connection.asBinder(); 178 } 179 180 @NonNull createHeader()181 InputConnectionCommandHeader createHeader() { 182 return new InputConnectionCommandHeader(mSessionId); 183 } 184 185 /** 186 * Invokes {@link IRemoteInputConnection#getTextAfterCursor(InputConnectionCommandHeader, int, 187 * int, AndroidFuture)}. 188 * 189 * @param length {@code length} parameter to be passed. 190 * @param flags {@code flags} parameter to be passed. 191 * @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation 192 * result. {@link RemoteException} will be treated as an error. 193 */ 194 @AnyThread 195 @NonNull getTextAfterCursor(int length, int flags)196 public AndroidFuture<CharSequence> getTextAfterCursor(int length, int flags) { 197 final AndroidFuture<CharSequence> future = new AndroidFuture<>(); 198 try { 199 mConnection.getTextAfterCursor(createHeader(), length, flags, future); 200 } catch (RemoteException e) { 201 future.completeExceptionally(e); 202 } 203 return future; 204 } 205 206 /** 207 * Invokes {@link IRemoteInputConnection#getTextBeforeCursor(InputConnectionCommandHeader, int, 208 * int, AndroidFuture)}. 209 * 210 * @param length {@code length} parameter to be passed. 211 * @param flags {@code flags} parameter to be passed. 212 * @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation 213 * result. {@link RemoteException} will be treated as an error. 214 */ 215 @AnyThread 216 @NonNull getTextBeforeCursor(int length, int flags)217 public AndroidFuture<CharSequence> getTextBeforeCursor(int length, int flags) { 218 final AndroidFuture<CharSequence> future = new AndroidFuture<>(); 219 try { 220 mConnection.getTextBeforeCursor(createHeader(), length, flags, future); 221 } catch (RemoteException e) { 222 future.completeExceptionally(e); 223 } 224 return future; 225 } 226 227 /** 228 * Invokes {@link IRemoteInputConnection#getSelectedText(InputConnectionCommandHeader, int, 229 * AndroidFuture)}. 230 * 231 * @param flags {@code flags} parameter to be passed. 232 * @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation 233 * result. {@link RemoteException} will be treated as an error. 234 */ 235 @AnyThread 236 @NonNull getSelectedText(int flags)237 public AndroidFuture<CharSequence> getSelectedText(int flags) { 238 final AndroidFuture<CharSequence> future = new AndroidFuture<>(); 239 try { 240 mConnection.getSelectedText(createHeader(), flags, future); 241 } catch (RemoteException e) { 242 future.completeExceptionally(e); 243 } 244 return future; 245 } 246 247 /** 248 * Invokes 249 * {@link IRemoteInputConnection#getSurroundingText(InputConnectionCommandHeader, int, int, int, 250 * AndroidFuture)}. 251 * 252 * @param beforeLength {@code beforeLength} parameter to be passed. 253 * @param afterLength {@code afterLength} parameter to be passed. 254 * @param flags {@code flags} parameter to be passed. 255 * @return {@link AndroidFuture<SurroundingText>} that can be used to retrieve the 256 * invocation result. {@link RemoteException} will be treated as an error. 257 */ 258 @AnyThread 259 @NonNull getSurroundingText(int beforeLength, int afterLength, int flags)260 public AndroidFuture<SurroundingText> getSurroundingText(int beforeLength, int afterLength, 261 int flags) { 262 final AndroidFuture<SurroundingText> future = new AndroidFuture<>(); 263 try { 264 mConnection.getSurroundingText(createHeader(), beforeLength, afterLength, flags, 265 future); 266 } catch (RemoteException e) { 267 future.completeExceptionally(e); 268 } 269 return future; 270 } 271 272 /** 273 * Invokes {@link IRemoteInputConnection#getCursorCapsMode(InputConnectionCommandHeader, int, 274 * AndroidFuture)}. 275 * 276 * @param reqModes {@code reqModes} parameter to be passed. 277 * @return {@link AndroidFuture<Integer>} that can be used to retrieve the invocation 278 * result. {@link RemoteException} will be treated as an error. 279 */ 280 @AnyThread 281 @NonNull getCursorCapsMode(int reqModes)282 public AndroidFuture<Integer> getCursorCapsMode(int reqModes) { 283 final AndroidFuture<Integer> future = new AndroidFuture<>(); 284 try { 285 mConnection.getCursorCapsMode(createHeader(), reqModes, future); 286 } catch (RemoteException e) { 287 future.completeExceptionally(e); 288 } 289 return future; 290 } 291 292 /** 293 * Invokes {@link IRemoteInputConnection#getExtractedText(InputConnectionCommandHeader, 294 * ExtractedTextRequest, int, AndroidFuture)}. 295 * 296 * @param request {@code request} parameter to be passed. 297 * @param flags {@code flags} parameter to be passed. 298 * @return {@link AndroidFuture<ExtractedText>} that can be used to retrieve the invocation 299 * result. {@link RemoteException} will be treated as an error. 300 */ 301 @AnyThread 302 @NonNull getExtractedText(ExtractedTextRequest request, int flags)303 public AndroidFuture<ExtractedText> getExtractedText(ExtractedTextRequest request, 304 int flags) { 305 final AndroidFuture<ExtractedText> future = new AndroidFuture<>(); 306 try { 307 mConnection.getExtractedText(createHeader(), request, flags, future); 308 } catch (RemoteException e) { 309 future.completeExceptionally(e); 310 } 311 return future; 312 } 313 314 /** 315 * Invokes 316 * {@link IRemoteInputConnection#commitText(InputConnectionCommandHeader, CharSequence, int)}. 317 * 318 * @param text {@code text} parameter to be passed. 319 * @param newCursorPosition {@code newCursorPosition} parameter to be passed. 320 * @return {@code true} if the invocation is completed without {@link RemoteException}. 321 * {@code false} otherwise. 322 */ 323 @AnyThread commitText(CharSequence text, int newCursorPosition)324 public boolean commitText(CharSequence text, int newCursorPosition) { 325 try { 326 mConnection.commitText(createHeader(), text, newCursorPosition); 327 return true; 328 } catch (RemoteException e) { 329 return false; 330 } 331 } 332 333 /** 334 * Invokes {@link IRemoteInputConnection#commitTextWithTextAttribute( 335 * InputConnectionCommandHeader, int, CharSequence)}. 336 * 337 * @param text {@code text} parameter to be passed. 338 * @param newCursorPosition {@code newCursorPosition} parameter to be passed. 339 * @param textAttribute The extra information about the text. 340 * @return {@code true} if the invocation is completed without {@link RemoteException}. 341 * {@code false} otherwise. 342 */ 343 @AnyThread commitText(CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)344 public boolean commitText(CharSequence text, int newCursorPosition, 345 @Nullable TextAttribute textAttribute) { 346 try { 347 mConnection.commitTextWithTextAttribute( 348 createHeader(), text, newCursorPosition, textAttribute); 349 return true; 350 } catch (RemoteException e) { 351 return false; 352 } 353 } 354 355 /** 356 * Invokes {@link IRemoteInputConnection#commitCompletion(InputConnectionCommandHeader, 357 * CompletionInfo)}. 358 * 359 * @param text {@code text} parameter to be passed. 360 * @return {@code true} if the invocation is completed without {@link RemoteException}. 361 * {@code false} otherwise. 362 */ 363 @AnyThread commitCompletion(CompletionInfo text)364 public boolean commitCompletion(CompletionInfo text) { 365 try { 366 mConnection.commitCompletion(createHeader(), text); 367 return true; 368 } catch (RemoteException e) { 369 return false; 370 } 371 } 372 373 /** 374 * Invokes {@link IRemoteInputConnection#commitCorrection(InputConnectionCommandHeader, 375 * CorrectionInfo)}. 376 * 377 * @param correctionInfo {@code correctionInfo} parameter to be passed. 378 * @return {@code true} if the invocation is completed without {@link RemoteException}. 379 * {@code false} otherwise. 380 */ 381 @AnyThread commitCorrection(CorrectionInfo correctionInfo)382 public boolean commitCorrection(CorrectionInfo correctionInfo) { 383 try { 384 mConnection.commitCorrection(createHeader(), correctionInfo); 385 return true; 386 } catch (RemoteException e) { 387 return false; 388 } 389 } 390 391 /** 392 * Invokes {@link IRemoteInputConnection#setSelection(InputConnectionCommandHeader, int, int)}. 393 * 394 * @param start {@code start} parameter to be passed. 395 * @param end {@code start} parameter to be passed. 396 * @return {@code true} if the invocation is completed without {@link RemoteException}. 397 * {@code false} otherwise. 398 */ 399 @AnyThread setSelection(int start, int end)400 public boolean setSelection(int start, int end) { 401 try { 402 mConnection.setSelection(createHeader(), start, end); 403 return true; 404 } catch (RemoteException e) { 405 return false; 406 } 407 } 408 409 /** 410 * Invokes 411 * {@link IRemoteInputConnection#performEditorAction(InputConnectionCommandHeader, int)}. 412 * 413 * @param actionCode {@code start} parameter to be passed. 414 * @return {@code true} if the invocation is completed without {@link RemoteException}. 415 * {@code false} otherwise. 416 */ 417 @AnyThread performEditorAction(int actionCode)418 public boolean performEditorAction(int actionCode) { 419 try { 420 mConnection.performEditorAction(createHeader(), actionCode); 421 return true; 422 } catch (RemoteException e) { 423 return false; 424 } 425 } 426 427 /** 428 * Invokes 429 * {@link IRemoteInputConnection#performContextMenuAction(InputConnectionCommandHeader, int)}. 430 * 431 * @param id {@code id} parameter to be passed. 432 * @return {@code true} if the invocation is completed without {@link RemoteException}. 433 * {@code false} otherwise. 434 */ 435 @AnyThread performContextMenuAction(int id)436 public boolean performContextMenuAction(int id) { 437 try { 438 mConnection.performContextMenuAction(createHeader(), id); 439 return true; 440 } catch (RemoteException e) { 441 return false; 442 } 443 } 444 445 /** 446 * Invokes 447 * {@link IRemoteInputConnection#setComposingRegion(InputConnectionCommandHeader, int, int)}. 448 * 449 * @param start {@code id} parameter to be passed. 450 * @param end {@code id} parameter to be passed. 451 * @return {@code true} if the invocation is completed without {@link RemoteException}. 452 * {@code false} otherwise. 453 */ 454 @AnyThread setComposingRegion(int start, int end)455 public boolean setComposingRegion(int start, int end) { 456 try { 457 mConnection.setComposingRegion(createHeader(), start, end); 458 return true; 459 } catch (RemoteException e) { 460 return false; 461 } 462 } 463 464 /** 465 * Invokes {@link IRemoteInputConnection#setComposingRegionWithTextAttribute( 466 * InputConnectionCommandHeader, int, int, TextAttribute)}. 467 * 468 * @param start {@code id} parameter to be passed. 469 * @param end {@code id} parameter to be passed. 470 * @param textAttribute The extra information about the text. 471 * @return {@code true} if the invocation is completed without {@link RemoteException}. 472 * {@code false} otherwise. 473 */ 474 @AnyThread setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute)475 public boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) { 476 try { 477 mConnection.setComposingRegionWithTextAttribute( 478 createHeader(), start, end, textAttribute); 479 return true; 480 } catch (RemoteException e) { 481 return false; 482 } 483 } 484 485 /** 486 * Invokes {@link IRemoteInputConnection#setComposingText(InputConnectionCommandHeader, 487 * CharSequence, int)}. 488 * 489 * @param text {@code text} parameter to be passed. 490 * @param newCursorPosition {@code newCursorPosition} parameter to be passed. 491 * @return {@code true} if the invocation is completed without {@link RemoteException}. 492 * {@code false} otherwise. 493 */ 494 @AnyThread setComposingText(CharSequence text, int newCursorPosition)495 public boolean setComposingText(CharSequence text, int newCursorPosition) { 496 try { 497 mConnection.setComposingText(createHeader(), text, newCursorPosition); 498 return true; 499 } catch (RemoteException e) { 500 return false; 501 } 502 } 503 504 /** 505 * Invokes {@link IRemoteInputConnection#setComposingTextWithTextAttribute( 506 * InputConnectionCommandHeader, CharSequence, int, TextAttribute)}. 507 * 508 * @param text {@code text} parameter to be passed. 509 * @param newCursorPosition {@code newCursorPosition} parameter to be passed. 510 * @param textAttribute The extra information about the text. 511 * @return {@code true} if the invocation is completed without {@link RemoteException}. 512 * {@code false} otherwise. 513 */ 514 @AnyThread setComposingText(CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)515 public boolean setComposingText(CharSequence text, int newCursorPosition, 516 @Nullable TextAttribute textAttribute) { 517 try { 518 mConnection.setComposingTextWithTextAttribute( 519 createHeader(), text, newCursorPosition, textAttribute); 520 return true; 521 } catch (RemoteException e) { 522 return false; 523 } 524 } 525 526 /** 527 * Invokes {@link IRemoteInputConnection#finishComposingText(InputConnectionCommandHeader)}. 528 * 529 * @return {@code true} if the invocation is completed without {@link RemoteException}. 530 * {@code false} otherwise. 531 */ 532 @AnyThread finishComposingText()533 public boolean finishComposingText() { 534 try { 535 mConnection.finishComposingText(createHeader()); 536 return true; 537 } catch (RemoteException e) { 538 return false; 539 } 540 } 541 542 /** 543 * Invokes {@link IRemoteInputConnection#beginBatchEdit(InputConnectionCommandHeader)}. 544 * 545 * @return {@code true} if the invocation is completed without {@link RemoteException}. 546 * {@code false} otherwise. 547 */ 548 @AnyThread beginBatchEdit()549 public boolean beginBatchEdit() { 550 try { 551 mConnection.beginBatchEdit(createHeader()); 552 return true; 553 } catch (RemoteException e) { 554 return false; 555 } 556 } 557 558 /** 559 * Invokes {@link IRemoteInputConnection#endBatchEdit(InputConnectionCommandHeader)}. 560 * 561 * @return {@code true} if the invocation is completed without {@link RemoteException}. 562 * {@code false} otherwise. 563 */ 564 @AnyThread endBatchEdit()565 public boolean endBatchEdit() { 566 try { 567 mConnection.endBatchEdit(createHeader()); 568 return true; 569 } catch (RemoteException e) { 570 return false; 571 } 572 } 573 574 /** 575 * Invokes {@link IRemoteInputConnection#sendKeyEvent(InputConnectionCommandHeader, KeyEvent)}. 576 * 577 * @param event {@code event} parameter to be passed. 578 * @return {@code true} if the invocation is completed without {@link RemoteException}. 579 * {@code false} otherwise. 580 */ 581 @AnyThread sendKeyEvent(KeyEvent event)582 public boolean sendKeyEvent(KeyEvent event) { 583 try { 584 mConnection.sendKeyEvent(createHeader(), event); 585 return true; 586 } catch (RemoteException e) { 587 return false; 588 } 589 } 590 591 /** 592 * Invokes {@link IRemoteInputConnection#clearMetaKeyStates(InputConnectionCommandHeader, int)}. 593 * 594 * @param states {@code states} parameter to be passed. 595 * @return {@code true} if the invocation is completed without {@link RemoteException}. 596 * {@code false} otherwise. 597 */ 598 @AnyThread clearMetaKeyStates(int states)599 public boolean clearMetaKeyStates(int states) { 600 try { 601 mConnection.clearMetaKeyStates(createHeader(), states); 602 return true; 603 } catch (RemoteException e) { 604 return false; 605 } 606 } 607 608 /** 609 * Invokes 610 * {@link IRemoteInputConnection#deleteSurroundingText(InputConnectionCommandHeader, int, int)}. 611 * 612 * @param beforeLength {@code beforeLength} parameter to be passed. 613 * @param afterLength {@code afterLength} parameter to be passed. 614 * @return {@code true} if the invocation is completed without {@link RemoteException}. 615 * {@code false} otherwise. 616 */ 617 @AnyThread deleteSurroundingText(int beforeLength, int afterLength)618 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 619 try { 620 mConnection.deleteSurroundingText(createHeader(), beforeLength, afterLength); 621 return true; 622 } catch (RemoteException e) { 623 return false; 624 } 625 } 626 627 /** 628 * Invokes {@link IRemoteInputConnection#deleteSurroundingTextInCodePoints( 629 * InputConnectionCommandHeader, int, int)}. 630 * 631 * @param beforeLength {@code beforeLength} parameter to be passed. 632 * @param afterLength {@code afterLength} parameter to be passed. 633 * @return {@code true} if the invocation is completed without {@link RemoteException}. 634 * {@code false} otherwise. 635 */ 636 @AnyThread deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)637 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { 638 try { 639 mConnection.deleteSurroundingTextInCodePoints(createHeader(), beforeLength, 640 afterLength); 641 return true; 642 } catch (RemoteException e) { 643 return false; 644 } 645 } 646 647 /** 648 * Invokes {@link IRemoteInputConnection#performSpellCheck(InputConnectionCommandHeader)}. 649 * 650 * @return {@code true} if the invocation is completed without {@link RemoteException}. 651 * {@code false} otherwise. 652 */ 653 @AnyThread performSpellCheck()654 public boolean performSpellCheck() { 655 try { 656 mConnection.performSpellCheck(createHeader()); 657 return true; 658 } catch (RemoteException e) { 659 return false; 660 } 661 } 662 663 /** 664 * Invokes {@link IRemoteInputConnection#performPrivateCommand(InputConnectionCommandHeader, 665 * String, Bundle)}. 666 * 667 * @param action {@code action} parameter to be passed. 668 * @param data {@code data} parameter to be passed. 669 * @return {@code true} if the invocation is completed without {@link RemoteException}. 670 * {@code false} otherwise. 671 */ 672 @AnyThread performPrivateCommand(String action, Bundle data)673 public boolean performPrivateCommand(String action, Bundle data) { 674 try { 675 mConnection.performPrivateCommand(createHeader(), action, data); 676 return true; 677 } catch (RemoteException e) { 678 return false; 679 } 680 } 681 682 /** 683 * Invokes {@link IRemoteInputConnection#performHandwritingGesture( 684 * InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}. 685 */ 686 @AnyThread performHandwritingGesture(@onNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer)687 public void performHandwritingGesture(@NonNull HandwritingGesture gesture, 688 @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) { 689 ResultReceiver resultReceiver = null; 690 if (consumer != null) { 691 Objects.requireNonNull(executor); 692 resultReceiver = new IntResultReceiver(executor, consumer); 693 } 694 try { 695 try (var ignored = getCancellationSignalBeamer().beamScopeIfNeeded(gesture)) { 696 mConnection.performHandwritingGesture(createHeader(), 697 ParcelableHandwritingGesture.of(gesture), 698 resultReceiver); 699 } 700 } catch (RemoteException e) { 701 if (consumer != null && executor != null) { 702 executor.execute(() -> consumer.accept( 703 InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED)); 704 } 705 } 706 } 707 708 /** 709 * Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture( 710 * InputConnectionCommandHeader, HandwritingGesture, IBinder)} 711 */ 712 @AnyThread previewHandwritingGesture( @onNull HandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)713 public boolean previewHandwritingGesture( 714 @NonNull HandwritingGesture gesture, 715 @Nullable CancellationSignal cancellationSignal) { 716 try { 717 try (var csToken = beam(cancellationSignal)) { 718 mConnection.previewHandwritingGesture(createHeader(), 719 ParcelableHandwritingGesture.of(gesture), 720 csToken); 721 } 722 return true; 723 } catch (RemoteException e) { 724 return false; 725 } 726 } 727 728 @Nullable beam(CancellationSignal cs)729 CancellationSignalBeamer.Sender.CloseableToken beam(CancellationSignal cs) { 730 if (cs == null) { 731 return null; 732 } 733 return getCancellationSignalBeamer().beam(cs); 734 } 735 getCancellationSignalBeamer()736 private CancellationSignalBeamer.Sender getCancellationSignalBeamer() { 737 if (mBeamer != null) { 738 return mBeamer; 739 } 740 mBeamer = new CancellationSignalBeamer.Sender() { 741 @Override 742 public void onCancel(IBinder token) { 743 try { 744 mConnection.cancelCancellationSignal(token); 745 } catch (RemoteException e) { 746 // Remote process likely died, ignore. 747 } 748 } 749 750 @Override 751 public void onForget(IBinder token) { 752 try { 753 mConnection.forgetCancellationSignal(token); 754 } catch (RemoteException e) { 755 // Remote process likely died, ignore. 756 } 757 } 758 }; 759 760 return mBeamer; 761 } 762 763 /** 764 * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int, 765 * int, AndroidFuture)}. 766 * 767 * @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed. 768 * @param imeDisplayId the display ID that is associated with the IME. 769 * @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation 770 * result. {@link RemoteException} will be treated as an error. 771 */ 772 @AnyThread 773 @NonNull requestCursorUpdates(int cursorUpdateMode, int imeDisplayId)774 public AndroidFuture<Boolean> requestCursorUpdates(int cursorUpdateMode, int imeDisplayId) { 775 final AndroidFuture<Boolean> future = new AndroidFuture<>(); 776 try { 777 mConnection.requestCursorUpdates(createHeader(), cursorUpdateMode, imeDisplayId, 778 future); 779 } catch (RemoteException e) { 780 future.completeExceptionally(e); 781 } 782 return future; 783 } 784 785 /** 786 * Invokes {@link IRemoteInputConnection#requestCursorUpdatesWithFilter( 787 * InputConnectionCommandHeader, int, int, int, AndroidFuture)}. 788 * 789 * @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed. 790 * @param cursorUpdateFilter {@code cursorUpdateFilter} parameter to be passed. 791 * @param imeDisplayId the display ID that is associated with the IME. 792 * @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation 793 * result. {@link RemoteException} will be treated as an error. 794 */ 795 @AnyThread 796 @NonNull requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId)797 public AndroidFuture<Boolean> requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter, 798 int imeDisplayId) { 799 final AndroidFuture<Boolean> future = new AndroidFuture<>(); 800 try { 801 mConnection.requestCursorUpdatesWithFilter(createHeader(), cursorUpdateMode, 802 cursorUpdateFilter, imeDisplayId, future); 803 } catch (RemoteException e) { 804 future.completeExceptionally(e); 805 } 806 return future; 807 } 808 809 /** 810 * Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader, 811 * RectF, ResultReceiver)} 812 * @param bounds {@code rectF} parameter to be passed. 813 * @param executor {@code Executor} parameter to be passed. 814 * @param consumer {@code Consumer} parameter to be passed. 815 */ 816 @AnyThread requestTextBoundsInfo( @onNull RectF bounds, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<TextBoundsInfoResult> consumer)817 public void requestTextBoundsInfo( 818 @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor, 819 @NonNull Consumer<TextBoundsInfoResult> consumer) { 820 Objects.requireNonNull(executor); 821 Objects.requireNonNull(consumer); 822 823 final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer); 824 try { 825 mConnection.requestTextBoundsInfo(createHeader(), bounds, resultReceiver); 826 } catch (RemoteException e) { 827 executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED))); 828 } 829 } 830 831 /** 832 * Invokes {@link IRemoteInputConnection#commitContent(InputConnectionCommandHeader, 833 * InputContentInfo, int, Bundle, AndroidFuture)}. 834 * 835 * @param inputContentInfo {@code inputContentInfo} parameter to be passed. 836 * @param flags {@code flags} parameter to be passed. 837 * @param opts {@code opts} parameter to be passed. 838 * @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation 839 * result. {@link RemoteException} will be treated as an error. 840 */ 841 @AnyThread 842 @NonNull commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)843 public AndroidFuture<Boolean> commitContent(InputContentInfo inputContentInfo, int flags, 844 Bundle opts) { 845 final AndroidFuture<Boolean> future = new AndroidFuture<>(); 846 try { 847 mConnection.commitContent(createHeader(), inputContentInfo, flags, opts, future); 848 } catch (RemoteException e) { 849 future.completeExceptionally(e); 850 } 851 return future; 852 } 853 854 /** 855 * Invokes 856 * {@link IRemoteInputConnection#setImeConsumesInput(InputConnectionCommandHeader, boolean)}. 857 * 858 * @param imeConsumesInput {@code imeConsumesInput} parameter to be passed. 859 * @return {@code true} if the invocation is completed without {@link RemoteException}. 860 * {@code false} otherwise. 861 */ 862 @AnyThread setImeConsumesInput(boolean imeConsumesInput)863 public boolean setImeConsumesInput(boolean imeConsumesInput) { 864 try { 865 mConnection.setImeConsumesInput(createHeader(), imeConsumesInput); 866 return true; 867 } catch (RemoteException e) { 868 return false; 869 } 870 } 871 872 /** 873 * Replaces the specific range in the current input field with suggested text. 874 * 875 * @param start the character index where the replacement should start. 876 * @param end the character index where the replacement should end. 877 * @param newCursorPosition the new cursor position around the text. If > 0, this is relative to 878 * the end of the text - 1; if <= 0, this is relative to the start of the text. So a value 879 * of 1 will always advance you to the position after the full text being inserted. Note 880 * that this means you can't position the cursor within the text. 881 * @param text the text to replace. This may include styles. 882 * @param textAttribute The extra information about the text. This value may be null. 883 * @return {@code true} if the specific range is replaced successfully, {@code false} otherwise. 884 * @see android.view.inputmethod.InputConnection#replaceText(int, int, CharSequence, int, 885 * TextAttribute) 886 */ 887 @AnyThread replaceText( int start, int end, @NonNull CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)888 public boolean replaceText( 889 int start, 890 int end, 891 @NonNull CharSequence text, 892 int newCursorPosition, 893 @Nullable TextAttribute textAttribute) { 894 try { 895 mConnection.replaceText( 896 createHeader(), start, end, text, newCursorPosition, textAttribute); 897 return true; 898 } catch (RemoteException e) { 899 return false; 900 } 901 } 902 } 903