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.view.inputmethod; 18 19 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetCursorCapsModeProto; 20 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetExtractedTextProto; 21 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetSelectedTextProto; 22 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetSurroundingTextProto; 23 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetTextAfterCursorProto; 24 import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetTextBeforeCursorProto; 25 26 import static java.lang.annotation.RetentionPolicy.SOURCE; 27 28 import android.annotation.AnyThread; 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.annotation.UiThread; 32 import android.app.UriGrantsManager; 33 import android.content.ContentProvider; 34 import android.content.Intent; 35 import android.graphics.RectF; 36 import android.net.Uri; 37 import android.os.Binder; 38 import android.os.Bundle; 39 import android.os.CancellationSignal; 40 import android.os.CancellationSignalBeamer; 41 import android.os.Handler; 42 import android.os.IBinder; 43 import android.os.Looper; 44 import android.os.ResultReceiver; 45 import android.os.Trace; 46 import android.os.UserHandle; 47 import android.util.Log; 48 import android.util.proto.ProtoOutputStream; 49 import android.view.KeyEvent; 50 import android.view.View; 51 import android.view.ViewRootImpl; 52 53 import com.android.internal.infra.AndroidFuture; 54 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; 55 import com.android.internal.inputmethod.IRemoteInputConnection; 56 import com.android.internal.inputmethod.ImeTracing; 57 import com.android.internal.inputmethod.InputConnectionCommandHeader; 58 59 import java.lang.annotation.Retention; 60 import java.lang.ref.WeakReference; 61 import java.util.concurrent.atomic.AtomicBoolean; 62 import java.util.concurrent.atomic.AtomicInteger; 63 import java.util.concurrent.atomic.AtomicReference; 64 import java.util.function.Function; 65 import java.util.function.Supplier; 66 67 /** 68 * Takes care of remote method invocations of {@link InputConnection} in the IME client side. 69 * 70 * <p>{@link android.inputmethodservice.RemoteInputConnection} code is executed in the IME process. 71 * It makes {@link IRemoteInputConnection} binder calls under the hood. 72 * {@link RemoteInputConnectionImpl} receives {@link IRemoteInputConnection} binder calls in the IME 73 * client (editor app) process, and forwards them to {@link InputConnection} that the IME client 74 * provided, on the {@link Looper} associated to the {@link InputConnection}.</p> 75 * 76 * <p>{@link com.android.internal.inputmethod.RemoteAccessibilityInputConnection} code is executed 77 * in the {@link android.accessibilityservice.AccessibilityService} process. It makes 78 * {@link com.android.internal.inputmethod.IRemoteAccessibilityInputConnection} binder calls under 79 * the hood. {@link #mAccessibilityInputConnection} receives the binder calls in the IME client 80 * (editor app) process, and forwards them to {@link InputConnection} that the IME client provided, 81 * on the {@link Looper} associated to the {@link InputConnection}.</p> 82 */ 83 final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { 84 private static final String TAG = "RemoteInputConnectionImpl"; 85 private static final boolean DEBUG = false; 86 87 /** 88 * An upper limit of calling {@link InputConnection#endBatchEdit()}. 89 * 90 * <p>This is a safeguard against broken {@link InputConnection#endBatchEdit()} implementations, 91 * which are real as we've seen in Bug 208941904. If the retry count reaches to the number 92 * defined here, we fall back into {@link InputMethodManager#restartInput(View)} as a 93 * workaround.</p> 94 */ 95 private static final int MAX_END_BATCH_EDIT_RETRY = 16; 96 97 /** 98 * A lightweight per-process type cache to remember classes that never returns {@code false} 99 * from {@link InputConnection#endBatchEdit()}. The implementation is optimized for simplicity 100 * and speed with accepting false-negatives in {@link #contains(Class)}. 101 */ 102 private static final class KnownAlwaysTrueEndBatchEditCache { 103 @Nullable 104 private static volatile Class<?> sElement; 105 @Nullable 106 private static volatile Class<?>[] sArray; 107 108 /** 109 * Query if the specified {@link InputConnection} implementation is known to be broken, with 110 * allowing false-negative results. 111 * 112 * @param klass An implementation class of {@link InputConnection} to be tested. 113 * @return {@code true} if the specified type was passed to {@link #add(Class)}. 114 * Note that there is a chance that you still receive {@code false} even if you 115 * called {@link #add(Class)} (false-negative). 116 */ 117 @AnyThread contains(@onNull Class<? extends InputConnection> klass)118 static boolean contains(@NonNull Class<? extends InputConnection> klass) { 119 if (klass == sElement) { 120 return true; 121 } 122 final Class<?>[] array = sArray; 123 if (array == null) { 124 return false; 125 } 126 for (Class<?> item : array) { 127 if (item == klass) { 128 return true; 129 } 130 } 131 return false; 132 } 133 134 /** 135 * Try to remember the specified {@link InputConnection} implementation as a known bad. 136 * 137 * <p>There is a chance that calling this method can accidentally overwrite existing 138 * cache entries. See the document of {@link #contains(Class)} for details.</p> 139 * 140 * @param klass The implementation class of {@link InputConnection} to be remembered. 141 */ 142 @AnyThread add(@onNull Class<? extends InputConnection> klass)143 static void add(@NonNull Class<? extends InputConnection> klass) { 144 if (sElement == null) { 145 // OK to accidentally overwrite an existing element that was set by another thread. 146 sElement = klass; 147 return; 148 } 149 150 final Class<?>[] array = sArray; 151 final int arraySize = array != null ? array.length : 0; 152 final Class<?>[] newArray = new Class<?>[arraySize + 1]; 153 for (int i = 0; i < arraySize; ++i) { 154 newArray[i] = array[i]; 155 } 156 newArray[arraySize] = klass; 157 158 // OK to accidentally overwrite an existing array that was set by another thread. 159 sArray = newArray; 160 } 161 } 162 163 @Retention(SOURCE) 164 private @interface Dispatching { cancellable()165 boolean cancellable(); 166 } 167 168 @NonNull 169 private final AtomicReference<InputConnection> mInputConnectionRef; 170 @NonNull 171 private final AtomicBoolean mDeactivateRequested = new AtomicBoolean(false); 172 173 @NonNull 174 private final Looper mLooper; 175 private final Handler mH; 176 177 private final InputMethodManager mParentInputMethodManager; 178 private final WeakReference<View> mServedView; 179 180 private final AtomicInteger mCurrentSessionId = new AtomicInteger(0); 181 private final AtomicBoolean mHasPendingInvalidation = new AtomicBoolean(); 182 183 private final AtomicBoolean mIsCursorAnchorInfoMonitoring = new AtomicBoolean(false); 184 private final AtomicBoolean mHasPendingImmediateCursorAnchorInfoUpdate = 185 new AtomicBoolean(false); 186 187 private CancellationSignalBeamer.Receiver mBeamer; 188 RemoteInputConnectionImpl(@onNull Looper looper, @NonNull InputConnection inputConnection, @NonNull InputMethodManager inputMethodManager, @Nullable View servedView)189 RemoteInputConnectionImpl(@NonNull Looper looper, 190 @NonNull InputConnection inputConnection, 191 @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) { 192 mInputConnectionRef = new AtomicReference<>(inputConnection); 193 mLooper = looper; 194 mH = new Handler(mLooper); 195 mParentInputMethodManager = inputMethodManager; 196 mServedView = new WeakReference<>(servedView); 197 } 198 199 /** 200 * @return {@link InputConnection} to which incoming IPCs will be dispatched. 201 */ 202 @Nullable getInputConnection()203 public InputConnection getInputConnection() { 204 return mInputConnectionRef.get(); 205 } 206 207 /** 208 * @return {@code true} if there is a pending {@link InputMethodManager#invalidateInput(View)} 209 * call. 210 */ hasPendingInvalidation()211 public boolean hasPendingInvalidation() { 212 return mHasPendingInvalidation.get(); 213 } 214 215 /** 216 * @return {@code true} until the target {@link InputConnection} receives 217 * {@link InputConnection#closeConnection()} as a result of {@link #deactivate()}. 218 */ isFinished()219 private boolean isFinished() { 220 return mInputConnectionRef.get() == null; 221 } 222 getServedView()223 private View getServedView() { 224 return mServedView.get(); 225 } 226 227 /** 228 * Queries if the given {@link View} is associated with this {@link RemoteInputConnectionImpl} 229 * or not. 230 * 231 * @param view {@link View}. 232 * @return {@code true} if the given {@link View} is not null and is associated with this 233 * {@link RemoteInputConnectionImpl}. 234 */ 235 @AnyThread isAssociatedWith(@ullable View view)236 public boolean isAssociatedWith(@Nullable View view) { 237 if (view == null) { 238 return false; 239 } 240 return mServedView.refersTo(view); 241 } 242 243 /** 244 * Gets and resets {@link #mHasPendingImmediateCursorAnchorInfoUpdate}. 245 * 246 * <p>Calling this method resets {@link #mHasPendingImmediateCursorAnchorInfoUpdate}. This 247 * means that the second call of this method returns {@code false} unless the IME requests 248 * {@link android.view.inputmethod.CursorAnchorInfo} again with 249 * {@link InputConnection#CURSOR_UPDATE_IMMEDIATE} flag.</p> 250 * 251 * @return {@code true} if there is any pending request for 252 * {@link android.view.inputmethod.CursorAnchorInfo} with 253 * {@link InputConnection#CURSOR_UPDATE_IMMEDIATE} flag. 254 */ 255 @AnyThread resetHasPendingImmediateCursorAnchorInfoUpdate()256 public boolean resetHasPendingImmediateCursorAnchorInfoUpdate() { 257 return mHasPendingImmediateCursorAnchorInfoUpdate.getAndSet(false); 258 } 259 260 /** 261 * @return {@code true} if there is any active request for 262 * {@link android.view.inputmethod.CursorAnchorInfo} with 263 * {@link InputConnection#CURSOR_UPDATE_MONITOR} flag. 264 */ 265 @AnyThread isCursorAnchorInfoMonitoring()266 public boolean isCursorAnchorInfoMonitoring() { 267 return mIsCursorAnchorInfoMonitoring.get(); 268 } 269 270 /** 271 * Schedule a task to execute 272 * {@link InputMethodManager#doInvalidateInput(RemoteInputConnectionImpl, TextSnapshot, int)} 273 * on the associated Handler if not yet scheduled. 274 * 275 * <p>By calling {@link InputConnection#takeSnapshot()} directly from the message loop, we can 276 * make sure that application code is not modifying text context in a reentrant manner.</p> 277 */ scheduleInvalidateInput()278 public void scheduleInvalidateInput() { 279 if (mHasPendingInvalidation.compareAndSet(false, true)) { 280 final int nextSessionId = mCurrentSessionId.incrementAndGet(); 281 // By calling InputConnection#takeSnapshot() directly from the message loop, we can make 282 // sure that application code is not modifying text context in a reentrant manner. 283 // e.g. We may see methods like EditText#setText() in the callstack here. 284 mH.post(() -> { 285 try { 286 if (isFinished()) { 287 // This is a stale request, which can happen. No need to show a warning 288 // because this situation itself is not an error. 289 return; 290 } 291 final InputConnection ic = getInputConnection(); 292 if (ic == null) { 293 // This is a stale request, which can happen. No need to show a warning 294 // because this situation itself is not an error. 295 return; 296 } 297 final View view = getServedView(); 298 if (view == null) { 299 // This is a stale request, which can happen. No need to show a warning 300 // because this situation itself is not an error. 301 return; 302 } 303 304 final Class<? extends InputConnection> icClass = ic.getClass(); 305 306 boolean alwaysTrueEndBatchEditDetected = 307 KnownAlwaysTrueEndBatchEditCache.contains(icClass); 308 309 if (!alwaysTrueEndBatchEditDetected) { 310 // Clean up composing text and batch edit. 311 final boolean supportsBatchEdit = ic.beginBatchEdit(); 312 ic.finishComposingText(); 313 if (supportsBatchEdit) { 314 // Also clean up batch edit. 315 int retryCount = 0; 316 while (true) { 317 if (!ic.endBatchEdit()) { 318 break; 319 } 320 ++retryCount; 321 if (retryCount > MAX_END_BATCH_EDIT_RETRY) { 322 Log.e(TAG, icClass.getTypeName() + "#endBatchEdit() still" 323 + " returns true even after retrying " 324 + MAX_END_BATCH_EDIT_RETRY + " times. Falling back to" 325 + " InputMethodManager#restartInput(View)"); 326 alwaysTrueEndBatchEditDetected = true; 327 KnownAlwaysTrueEndBatchEditCache.add(icClass); 328 break; 329 } 330 } 331 } 332 } 333 334 if (!alwaysTrueEndBatchEditDetected) { 335 final TextSnapshot textSnapshot = ic.takeSnapshot(); 336 if (textSnapshot != null && mParentInputMethodManager.doInvalidateInput( 337 this, textSnapshot, nextSessionId)) { 338 return; 339 } 340 } 341 342 mParentInputMethodManager.restartInput(view); 343 } finally { 344 mHasPendingInvalidation.set(false); 345 } 346 }); 347 } 348 } 349 350 /** 351 * Called when this object needs to be permanently deactivated. 352 * 353 * <p>Multiple invocations will be simply ignored.</p> 354 */ 355 @Dispatching(cancellable = false) deactivate()356 public void deactivate() { 357 if (mDeactivateRequested.getAndSet(true)) { 358 // This is a small performance optimization. Still only the 1st call of 359 // deactivate() will take effect. 360 return; 361 } 362 dispatch(() -> { 363 Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection"); 364 try { 365 InputConnection ic = getInputConnection(); 366 if (ic == null) { 367 return; 368 } 369 try { 370 ic.closeConnection(); 371 } catch (AbstractMethodError ignored) { 372 // TODO(b/199934664): See if we can remove this by providing a default impl. 373 } 374 } finally { 375 mInputConnectionRef.set(null); 376 Trace.traceEnd(Trace.TRACE_TAG_INPUT); 377 } 378 379 // Notify the app that the InputConnection was closed. 380 final View servedView = mServedView.get(); 381 if (servedView != null) { 382 final Handler handler = servedView.getHandler(); 383 // The handler is null if the view is already detached. When that's the case, for 384 // now, we simply don't dispatch this callback. 385 if (handler != null) { 386 if (DEBUG) { 387 Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView); 388 } 389 if (handler.getLooper().isCurrentThread()) { 390 servedView.onInputConnectionClosedInternal(); 391 } else { 392 handler.post(servedView::onInputConnectionClosedInternal); 393 } 394 } 395 396 final ViewRootImpl viewRoot = servedView.getViewRootImpl(); 397 if (viewRoot != null) { 398 viewRoot.getHandwritingInitiator().onInputConnectionClosed(servedView); 399 } 400 } 401 }); 402 } 403 404 @Dispatching(cancellable = false) 405 @Override cancelCancellationSignal(IBinder token)406 public void cancelCancellationSignal(IBinder token) { 407 if (mBeamer == null) { 408 return; 409 } 410 dispatch(() -> { 411 mBeamer.cancel(token); 412 }); 413 } 414 415 @Override forgetCancellationSignal(IBinder token)416 public void forgetCancellationSignal(IBinder token) { 417 if (mBeamer == null) { 418 return; 419 } 420 dispatch(() -> { 421 mBeamer.forget(token); 422 }); 423 } 424 425 @Override toString()426 public String toString() { 427 return "RemoteInputConnectionImpl{" 428 + "connection=" + getInputConnection() 429 + " mDeactivateRequested=" + mDeactivateRequested.get() 430 + " mServedView=" + mServedView.get() 431 + "}"; 432 } 433 434 /** 435 * Called by {@link InputMethodManager} to dump the editor state. 436 * 437 * @param proto {@link ProtoOutputStream} to which the editor state should be dumped. 438 * @param fieldId the ID to be passed to 439 * {@link DumpableInputConnection#dumpDebug(ProtoOutputStream, long)}. 440 */ dumpDebug(ProtoOutputStream proto, long fieldId)441 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 442 final InputConnection ic = mInputConnectionRef.get(); 443 // Check that the call is initiated in the target thread of the current InputConnection 444 // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are 445 // executed on this thread. Otherwise the messages are dispatched to the correct thread 446 // in IInputConnectionWrapper, but this is not wanted while dumping, for performance 447 // reasons. 448 if ((ic instanceof DumpableInputConnection) && mLooper.isCurrentThread()) { 449 ((DumpableInputConnection) ic).dumpDebug(proto, fieldId); 450 } 451 } 452 453 /** 454 * Invoke {@link InputConnection#reportFullscreenMode(boolean)} or schedule it on the target 455 * thread associated with {@link InputConnection#getHandler()}. 456 * 457 * @param enabled the parameter to be passed to 458 * {@link InputConnection#reportFullscreenMode(boolean)}. 459 */ 460 @Dispatching(cancellable = false) dispatchReportFullscreenMode(boolean enabled)461 public void dispatchReportFullscreenMode(boolean enabled) { 462 dispatch(() -> { 463 final InputConnection ic = getInputConnection(); 464 if (ic == null || mDeactivateRequested.get()) { 465 return; 466 } 467 ic.reportFullscreenMode(enabled); 468 }); 469 } 470 471 @Dispatching(cancellable = true) 472 @Override getTextAfterCursor(InputConnectionCommandHeader header, int length, int flags, AndroidFuture future )473 public void getTextAfterCursor(InputConnectionCommandHeader header, int length, int flags, 474 AndroidFuture future /* T=CharSequence */) { 475 dispatchWithTracing("getTextAfterCursor", future, () -> { 476 if (header.mSessionId != mCurrentSessionId.get()) { 477 return null; // cancelled 478 } 479 final InputConnection ic = getInputConnection(); 480 if (ic == null || mDeactivateRequested.get()) { 481 Log.w(TAG, "getTextAfterCursor on inactive InputConnection"); 482 return null; 483 } 484 if (length < 0) { 485 Log.i(TAG, "Returning null to getTextAfterCursor due to an invalid length=" 486 + length); 487 return null; 488 } 489 return ic.getTextAfterCursor(length, flags); 490 }, useImeTracing() ? result -> buildGetTextAfterCursorProto(length, flags, result) : null); 491 } 492 493 @Dispatching(cancellable = true) 494 @Override getTextBeforeCursor(InputConnectionCommandHeader header, int length, int flags, AndroidFuture future )495 public void getTextBeforeCursor(InputConnectionCommandHeader header, int length, int flags, 496 AndroidFuture future /* T=CharSequence */) { 497 dispatchWithTracing("getTextBeforeCursor", future, () -> { 498 if (header.mSessionId != mCurrentSessionId.get()) { 499 return null; // cancelled 500 } 501 final InputConnection ic = getInputConnection(); 502 if (ic == null || mDeactivateRequested.get()) { 503 Log.w(TAG, "getTextBeforeCursor on inactive InputConnection"); 504 return null; 505 } 506 if (length < 0) { 507 Log.i(TAG, "Returning null to getTextBeforeCursor due to an invalid length=" 508 + length); 509 return null; 510 } 511 return ic.getTextBeforeCursor(length, flags); 512 }, useImeTracing() ? result -> buildGetTextBeforeCursorProto(length, flags, result) : null); 513 } 514 515 @Dispatching(cancellable = true) 516 @Override getSelectedText(InputConnectionCommandHeader header, int flags, AndroidFuture future )517 public void getSelectedText(InputConnectionCommandHeader header, int flags, 518 AndroidFuture future /* T=CharSequence */) { 519 dispatchWithTracing("getSelectedText", future, () -> { 520 if (header.mSessionId != mCurrentSessionId.get()) { 521 return null; // cancelled 522 } 523 final InputConnection ic = getInputConnection(); 524 if (ic == null || mDeactivateRequested.get()) { 525 Log.w(TAG, "getSelectedText on inactive InputConnection"); 526 return null; 527 } 528 try { 529 return ic.getSelectedText(flags); 530 } catch (AbstractMethodError ignored) { 531 // TODO(b/199934664): See if we can remove this by providing a default impl. 532 return null; 533 } 534 }, useImeTracing() ? result -> buildGetSelectedTextProto(flags, result) : null); 535 } 536 537 @Dispatching(cancellable = true) 538 @Override getSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength, int flags, AndroidFuture future )539 public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength, 540 int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) { 541 dispatchWithTracing("getSurroundingText", future, () -> { 542 if (header.mSessionId != mCurrentSessionId.get()) { 543 return null; // cancelled 544 } 545 final InputConnection ic = getInputConnection(); 546 if (ic == null || mDeactivateRequested.get()) { 547 Log.w(TAG, "getSurroundingText on inactive InputConnection"); 548 return null; 549 } 550 if (beforeLength < 0) { 551 Log.i(TAG, "Returning null to getSurroundingText due to an invalid" 552 + " beforeLength=" + beforeLength); 553 return null; 554 } 555 if (afterLength < 0) { 556 Log.i(TAG, "Returning null to getSurroundingText due to an invalid" 557 + " afterLength=" + afterLength); 558 return null; 559 } 560 return ic.getSurroundingText(beforeLength, afterLength, flags); 561 }, useImeTracing() ? result -> buildGetSurroundingTextProto( 562 beforeLength, afterLength, flags, result) : null); 563 } 564 565 @Dispatching(cancellable = true) 566 @Override getCursorCapsMode(InputConnectionCommandHeader header, int reqModes, AndroidFuture future )567 public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes, 568 AndroidFuture future /* T=Integer */) { 569 dispatchWithTracing("getCursorCapsMode", future, () -> { 570 if (header.mSessionId != mCurrentSessionId.get()) { 571 return 0; // cancelled 572 } 573 final InputConnection ic = getInputConnection(); 574 if (ic == null || mDeactivateRequested.get()) { 575 Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); 576 return 0; 577 } 578 return ic.getCursorCapsMode(reqModes); 579 }, useImeTracing() ? result -> buildGetCursorCapsModeProto(reqModes, result) : null); 580 } 581 582 @Dispatching(cancellable = true) 583 @Override getExtractedText(InputConnectionCommandHeader header, ExtractedTextRequest request, int flags, AndroidFuture future )584 public void getExtractedText(InputConnectionCommandHeader header, ExtractedTextRequest request, 585 int flags, AndroidFuture future /* T=ExtractedText */) { 586 dispatchWithTracing("getExtractedText", future, () -> { 587 if (header.mSessionId != mCurrentSessionId.get()) { 588 return null; // cancelled 589 } 590 final InputConnection ic = getInputConnection(); 591 if (ic == null || mDeactivateRequested.get()) { 592 Log.w(TAG, "getExtractedText on inactive InputConnection"); 593 return null; 594 } 595 return ic.getExtractedText(request, flags); 596 }, useImeTracing() ? result -> buildGetExtractedTextProto(request, flags, result) : null); 597 } 598 599 @Dispatching(cancellable = true) 600 @Override commitText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition)601 public void commitText(InputConnectionCommandHeader header, CharSequence text, 602 int newCursorPosition) { 603 dispatchWithTracing("commitText", () -> { 604 if (header.mSessionId != mCurrentSessionId.get()) { 605 return; // cancelled 606 } 607 InputConnection ic = getInputConnection(); 608 if (ic == null || mDeactivateRequested.get()) { 609 Log.w(TAG, "commitText on inactive InputConnection"); 610 return; 611 } 612 ic.commitText(text, newCursorPosition); 613 }); 614 } 615 616 @Dispatching(cancellable = true) 617 @Override commitTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)618 public void commitTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text, 619 int newCursorPosition, @Nullable TextAttribute textAttribute) { 620 dispatchWithTracing("commitTextWithTextAttribute", () -> { 621 if (header.mSessionId != mCurrentSessionId.get()) { 622 return; // cancelled 623 } 624 InputConnection ic = getInputConnection(); 625 if (ic == null || mDeactivateRequested.get()) { 626 Log.w(TAG, "commitText on inactive InputConnection"); 627 return; 628 } 629 ic.commitText(text, newCursorPosition, textAttribute); 630 }); 631 } 632 633 @Dispatching(cancellable = true) 634 @Override commitCompletion(InputConnectionCommandHeader header, CompletionInfo text)635 public void commitCompletion(InputConnectionCommandHeader header, CompletionInfo text) { 636 dispatchWithTracing("commitCompletion", () -> { 637 if (header.mSessionId != mCurrentSessionId.get()) { 638 return; // cancelled 639 } 640 InputConnection ic = getInputConnection(); 641 if (ic == null || mDeactivateRequested.get()) { 642 Log.w(TAG, "commitCompletion on inactive InputConnection"); 643 return; 644 } 645 ic.commitCompletion(text); 646 }); 647 } 648 649 @Dispatching(cancellable = true) 650 @Override commitCorrection(InputConnectionCommandHeader header, CorrectionInfo info)651 public void commitCorrection(InputConnectionCommandHeader header, CorrectionInfo info) { 652 dispatchWithTracing("commitCorrection", () -> { 653 if (header.mSessionId != mCurrentSessionId.get()) { 654 return; // cancelled 655 } 656 InputConnection ic = getInputConnection(); 657 if (ic == null || mDeactivateRequested.get()) { 658 Log.w(TAG, "commitCorrection on inactive InputConnection"); 659 return; 660 } 661 try { 662 ic.commitCorrection(info); 663 } catch (AbstractMethodError ignored) { 664 // TODO(b/199934664): See if we can remove this by providing a default impl. 665 } 666 }); 667 } 668 669 @Dispatching(cancellable = true) 670 @Override setSelection(InputConnectionCommandHeader header, int start, int end)671 public void setSelection(InputConnectionCommandHeader header, int start, int end) { 672 dispatchWithTracing("setSelection", () -> { 673 if (header.mSessionId != mCurrentSessionId.get()) { 674 return; // cancelled 675 } 676 InputConnection ic = getInputConnection(); 677 if (ic == null || mDeactivateRequested.get()) { 678 Log.w(TAG, "setSelection on inactive InputConnection"); 679 return; 680 } 681 ic.setSelection(start, end); 682 }); 683 } 684 685 @Dispatching(cancellable = true) 686 @Override performEditorAction(InputConnectionCommandHeader header, int id)687 public void performEditorAction(InputConnectionCommandHeader header, int id) { 688 dispatchWithTracing("performEditorAction", () -> { 689 if (header.mSessionId != mCurrentSessionId.get()) { 690 return; // cancelled 691 } 692 InputConnection ic = getInputConnection(); 693 if (ic == null || mDeactivateRequested.get()) { 694 Log.w(TAG, "performEditorAction on inactive InputConnection"); 695 return; 696 } 697 ic.performEditorAction(id); 698 }); 699 } 700 701 @Dispatching(cancellable = true) 702 @Override performContextMenuAction(InputConnectionCommandHeader header, int id)703 public void performContextMenuAction(InputConnectionCommandHeader header, int id) { 704 dispatchWithTracing("performContextMenuAction", () -> { 705 if (header.mSessionId != mCurrentSessionId.get()) { 706 return; // cancelled 707 } 708 InputConnection ic = getInputConnection(); 709 if (ic == null || mDeactivateRequested.get()) { 710 Log.w(TAG, "performContextMenuAction on inactive InputConnection"); 711 return; 712 } 713 ic.performContextMenuAction(id); 714 }); 715 } 716 717 @Dispatching(cancellable = true) 718 @Override setComposingRegion(InputConnectionCommandHeader header, int start, int end)719 public void setComposingRegion(InputConnectionCommandHeader header, int start, int end) { 720 dispatchWithTracing("setComposingRegion", () -> { 721 if (header.mSessionId != mCurrentSessionId.get()) { 722 return; // cancelled 723 } 724 InputConnection ic = getInputConnection(); 725 if (ic == null || mDeactivateRequested.get()) { 726 Log.w(TAG, "setComposingRegion on inactive InputConnection"); 727 return; 728 } 729 try { 730 ic.setComposingRegion(start, end); 731 } catch (AbstractMethodError ignored) { 732 // TODO(b/199934664): See if we can remove this by providing a default impl. 733 } 734 }); 735 } 736 737 @Dispatching(cancellable = true) 738 @Override setComposingRegionWithTextAttribute(InputConnectionCommandHeader header, int start, int end, @Nullable TextAttribute textAttribute)739 public void setComposingRegionWithTextAttribute(InputConnectionCommandHeader header, int start, 740 int end, @Nullable TextAttribute textAttribute) { 741 dispatchWithTracing("setComposingRegionWithTextAttribute", () -> { 742 if (header.mSessionId != mCurrentSessionId.get()) { 743 return; // cancelled 744 } 745 InputConnection ic = getInputConnection(); 746 if (ic == null || mDeactivateRequested.get()) { 747 Log.w(TAG, "setComposingRegion on inactive InputConnection"); 748 return; 749 } 750 ic.setComposingRegion(start, end, textAttribute); 751 }); 752 } 753 754 @Dispatching(cancellable = true) 755 @Override setComposingText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition)756 public void setComposingText(InputConnectionCommandHeader header, CharSequence text, 757 int newCursorPosition) { 758 dispatchWithTracing("setComposingText", () -> { 759 if (header.mSessionId != mCurrentSessionId.get()) { 760 return; // cancelled 761 } 762 InputConnection ic = getInputConnection(); 763 if (ic == null || mDeactivateRequested.get()) { 764 Log.w(TAG, "setComposingText on inactive InputConnection"); 765 return; 766 } 767 ic.setComposingText(text, newCursorPosition); 768 }); 769 } 770 771 @Dispatching(cancellable = true) 772 @Override setComposingTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)773 public void setComposingTextWithTextAttribute(InputConnectionCommandHeader header, 774 CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute) { 775 dispatchWithTracing("setComposingTextWithTextAttribute", () -> { 776 if (header.mSessionId != mCurrentSessionId.get()) { 777 return; // cancelled 778 } 779 InputConnection ic = getInputConnection(); 780 if (ic == null || mDeactivateRequested.get()) { 781 Log.w(TAG, "setComposingText on inactive InputConnection"); 782 return; 783 } 784 ic.setComposingText(text, newCursorPosition, textAttribute); 785 }); 786 } 787 788 /** 789 * Dispatches {@link InputConnection#finishComposingText()}. 790 * 791 * <p>This method is intended to be called only from {@link InputMethodManager}.</p> 792 */ 793 @Dispatching(cancellable = true) finishComposingTextFromImm()794 public void finishComposingTextFromImm() { 795 final int currentSessionId = mCurrentSessionId.get(); 796 dispatchWithTracing("finishComposingTextFromImm", () -> { 797 if (isFinished()) { 798 // In this case, #finishComposingText() is guaranteed to be called already. 799 // There should be no negative impact if we ignore this call silently. 800 if (DEBUG) { 801 Log.w(TAG, "Bug 35301295: Redundant finishComposingTextFromImm."); 802 } 803 return; 804 } 805 if (currentSessionId != mCurrentSessionId.get()) { 806 return; // cancelled 807 } 808 InputConnection ic = getInputConnection(); 809 if (ic == null || mDeactivateRequested.get()) { 810 Log.w(TAG, "finishComposingTextFromImm on inactive InputConnection"); 811 return; 812 } 813 ic.finishComposingText(); 814 }); 815 } 816 817 @Dispatching(cancellable = true) 818 @Override finishComposingText(InputConnectionCommandHeader header)819 public void finishComposingText(InputConnectionCommandHeader header) { 820 dispatchWithTracing("finishComposingText", () -> { 821 if (isFinished()) { 822 // In this case, #finishComposingText() is guaranteed to be called already. 823 // There should be no negative impact if we ignore this call silently. 824 if (DEBUG) { 825 Log.w(TAG, "Bug 35301295: Redundant finishComposingText."); 826 } 827 return; 828 } 829 if (header.mSessionId != mCurrentSessionId.get()) { 830 return; // cancelled 831 } 832 InputConnection ic = getInputConnection(); 833 if (ic == null && mDeactivateRequested.get()) { 834 Log.w(TAG, "finishComposingText on inactive InputConnection"); 835 return; 836 } 837 ic.finishComposingText(); 838 }); 839 } 840 841 @Dispatching(cancellable = true) 842 @Override sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event)843 public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) { 844 dispatchWithTracing("sendKeyEvent", () -> { 845 if (header.mSessionId != mCurrentSessionId.get()) { 846 return; // cancelled 847 } 848 InputConnection ic = getInputConnection(); 849 if (ic == null || mDeactivateRequested.get()) { 850 Log.w(TAG, "sendKeyEvent on inactive InputConnection"); 851 return; 852 } 853 ic.sendKeyEvent(event); 854 }); 855 } 856 857 @Dispatching(cancellable = true) 858 @Override clearMetaKeyStates(InputConnectionCommandHeader header, int states)859 public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) { 860 dispatchWithTracing("clearMetaKeyStates", () -> { 861 if (header.mSessionId != mCurrentSessionId.get()) { 862 return; // cancelled 863 } 864 InputConnection ic = getInputConnection(); 865 if (ic == null || mDeactivateRequested.get()) { 866 Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); 867 return; 868 } 869 ic.clearMetaKeyStates(states); 870 }); 871 } 872 873 @Dispatching(cancellable = true) 874 @Override deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength)875 public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength, 876 int afterLength) { 877 dispatchWithTracing("deleteSurroundingText", () -> { 878 if (header.mSessionId != mCurrentSessionId.get()) { 879 return; // cancelled 880 } 881 InputConnection ic = getInputConnection(); 882 if (ic == null || mDeactivateRequested.get()) { 883 Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); 884 return; 885 } 886 ic.deleteSurroundingText(beforeLength, afterLength); 887 }); 888 } 889 890 @Dispatching(cancellable = true) 891 @Override deleteSurroundingTextInCodePoints(InputConnectionCommandHeader header, int beforeLength, int afterLength)892 public void deleteSurroundingTextInCodePoints(InputConnectionCommandHeader header, 893 int beforeLength, int afterLength) { 894 dispatchWithTracing("deleteSurroundingTextInCodePoints", () -> { 895 if (header.mSessionId != mCurrentSessionId.get()) { 896 return; // cancelled 897 } 898 InputConnection ic = getInputConnection(); 899 if (ic == null || mDeactivateRequested.get()) { 900 Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection"); 901 return; 902 } 903 try { 904 ic.deleteSurroundingTextInCodePoints(beforeLength, afterLength); 905 } catch (AbstractMethodError ignored) { 906 // TODO(b/199934664): See if we can remove this by providing a default impl. 907 } 908 }); 909 } 910 911 @Dispatching(cancellable = true) 912 @Override beginBatchEdit(InputConnectionCommandHeader header)913 public void beginBatchEdit(InputConnectionCommandHeader header) { 914 dispatchWithTracing("beginBatchEdit", () -> { 915 if (header.mSessionId != mCurrentSessionId.get()) { 916 return; // cancelled 917 } 918 InputConnection ic = getInputConnection(); 919 if (ic == null || mDeactivateRequested.get()) { 920 Log.w(TAG, "beginBatchEdit on inactive InputConnection"); 921 return; 922 } 923 ic.beginBatchEdit(); 924 }); 925 } 926 927 @Dispatching(cancellable = true) 928 @Override endBatchEdit(InputConnectionCommandHeader header)929 public void endBatchEdit(InputConnectionCommandHeader header) { 930 dispatchWithTracing("endBatchEdit", () -> { 931 if (header.mSessionId != mCurrentSessionId.get()) { 932 return; // cancelled 933 } 934 InputConnection ic = getInputConnection(); 935 if (ic == null || mDeactivateRequested.get()) { 936 Log.w(TAG, "endBatchEdit on inactive InputConnection"); 937 return; 938 } 939 ic.endBatchEdit(); 940 }); 941 } 942 943 @Dispatching(cancellable = true) 944 @Override performSpellCheck(InputConnectionCommandHeader header)945 public void performSpellCheck(InputConnectionCommandHeader header) { 946 dispatchWithTracing("performSpellCheck", () -> { 947 if (header.mSessionId != mCurrentSessionId.get()) { 948 return; // cancelled 949 } 950 InputConnection ic = getInputConnection(); 951 if (ic == null || mDeactivateRequested.get()) { 952 Log.w(TAG, "performSpellCheck on inactive InputConnection"); 953 return; 954 } 955 ic.performSpellCheck(); 956 }); 957 } 958 959 @Dispatching(cancellable = true) 960 @Override performPrivateCommand(InputConnectionCommandHeader header, String action, Bundle data)961 public void performPrivateCommand(InputConnectionCommandHeader header, String action, 962 Bundle data) { 963 dispatchWithTracing("performPrivateCommand", () -> { 964 if (header.mSessionId != mCurrentSessionId.get()) { 965 return; // cancelled 966 } 967 InputConnection ic = getInputConnection(); 968 if (ic == null || mDeactivateRequested.get()) { 969 Log.w(TAG, "performPrivateCommand on inactive InputConnection"); 970 return; 971 } 972 ic.performPrivateCommand(action, data); 973 }); 974 } 975 976 @Dispatching(cancellable = true) 977 @Override performHandwritingGesture( InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer, ResultReceiver resultReceiver)978 public void performHandwritingGesture( 979 InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer, 980 ResultReceiver resultReceiver) { 981 final HandwritingGesture gesture = gestureContainer.get(); 982 if (gesture instanceof CancellableHandwritingGesture) { 983 // For cancellable gestures, unbeam and save the CancellationSignal. 984 CancellableHandwritingGesture cancellableGesture = 985 (CancellableHandwritingGesture) gesture; 986 cancellableGesture.unbeamCancellationSignal(getCancellationSignalBeamer()); 987 if (cancellableGesture.getCancellationSignal() != null 988 && cancellableGesture.getCancellationSignal().isCanceled()) { 989 // Send result for canceled operations. 990 if (resultReceiver != null) { 991 resultReceiver.send( 992 InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED, null); 993 } 994 return; 995 } 996 } 997 dispatchWithTracing("performHandwritingGesture", () -> { 998 if (header.mSessionId != mCurrentSessionId.get()) { 999 if (resultReceiver != null) { 1000 resultReceiver.send( 1001 InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED, null); 1002 } 1003 return; // cancelled 1004 } 1005 InputConnection ic = getInputConnection(); 1006 if (ic == null || mDeactivateRequested.get()) { 1007 Log.w(TAG, "performHandwritingGesture on inactive InputConnection"); 1008 if (resultReceiver != null) { 1009 resultReceiver.send( 1010 InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED, null); 1011 } 1012 return; 1013 } 1014 1015 // TODO(210039666): implement Cleaner to return HANDWRITING_GESTURE_RESULT_UNKNOWN if 1016 // editor doesn't return any type. 1017 ic.performHandwritingGesture( 1018 gesture, 1019 resultReceiver != null ? Runnable::run : null, 1020 resultReceiver != null 1021 ? (resultCode) -> resultReceiver.send(resultCode, null /* resultData */) 1022 : null); 1023 }); 1024 } 1025 1026 @Dispatching(cancellable = true) 1027 @Override previewHandwritingGesture( InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer, IBinder cancellationSignalToken)1028 public void previewHandwritingGesture( 1029 InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer, 1030 IBinder cancellationSignalToken) { 1031 final CancellationSignal cancellationSignal = 1032 cancellationSignalToken != null 1033 ? getCancellationSignalBeamer().unbeam(cancellationSignalToken) : null; 1034 1035 // Previews always use PreviewableHandwritingGesture but if incorrectly wrong class is 1036 // passed, ClassCastException will be sent back to caller. 1037 final PreviewableHandwritingGesture gesture = 1038 (PreviewableHandwritingGesture) gestureContainer.get(); 1039 1040 dispatchWithTracing("previewHandwritingGesture", () -> { 1041 if (header.mSessionId != mCurrentSessionId.get() 1042 || (cancellationSignal != null && cancellationSignal.isCanceled())) { 1043 return; // cancelled 1044 } 1045 InputConnection ic = getInputConnection(); 1046 if (ic == null || mDeactivateRequested.get()) { 1047 Log.w(TAG, "previewHandwritingGesture on inactive InputConnection"); 1048 return; // cancelled 1049 } 1050 1051 ic.previewHandwritingGesture(gesture, cancellationSignal); 1052 }); 1053 } 1054 getCancellationSignalBeamer()1055 private CancellationSignalBeamer.Receiver getCancellationSignalBeamer() { 1056 if (mBeamer != null) { 1057 return mBeamer; 1058 } 1059 mBeamer = new CancellationSignalBeamer.Receiver(true /* cancelOnSenderDeath */); 1060 return mBeamer; 1061 } 1062 1063 @Dispatching(cancellable = true) 1064 @Override requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode, int imeDisplayId, AndroidFuture future )1065 public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode, 1066 int imeDisplayId, AndroidFuture future /* T=Boolean */) { 1067 dispatchWithTracing("requestCursorUpdates", future, () -> { 1068 if (header.mSessionId != mCurrentSessionId.get()) { 1069 return false; // cancelled 1070 } 1071 return requestCursorUpdatesInternal( 1072 cursorUpdateMode, 0 /* cursorUpdateFilter */, imeDisplayId); 1073 }); 1074 } 1075 1076 @Dispatching(cancellable = true) 1077 @Override requestCursorUpdatesWithFilter(InputConnectionCommandHeader header, int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId, AndroidFuture future )1078 public void requestCursorUpdatesWithFilter(InputConnectionCommandHeader header, 1079 int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId, 1080 AndroidFuture future /* T=Boolean */) { 1081 dispatchWithTracing("requestCursorUpdates", future, () -> { 1082 if (header.mSessionId != mCurrentSessionId.get()) { 1083 return false; // cancelled 1084 } 1085 return requestCursorUpdatesInternal( 1086 cursorUpdateMode, cursorUpdateFilter, imeDisplayId); 1087 }); 1088 } 1089 requestCursorUpdatesInternal( @nputConnection.CursorUpdateMode int cursorUpdateMode, @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId)1090 private boolean requestCursorUpdatesInternal( 1091 @InputConnection.CursorUpdateMode int cursorUpdateMode, 1092 @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) { 1093 final InputConnection ic = getInputConnection(); 1094 if (ic == null || mDeactivateRequested.get()) { 1095 Log.w(TAG, "requestCursorUpdates on inactive InputConnection"); 1096 return false; 1097 } 1098 if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get() 1099 && mParentInputMethodManager.getDisplayId() != imeDisplayId) { 1100 // requestCursorUpdates() is not currently supported across displays. 1101 return false; 1102 } 1103 final boolean hasImmediate = 1104 (cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0; 1105 final boolean hasMonitoring = 1106 (cursorUpdateMode & InputConnection.CURSOR_UPDATE_MONITOR) != 0; 1107 boolean result = false; 1108 try { 1109 result = ic.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter); 1110 return result; 1111 } catch (AbstractMethodError ignored) { 1112 // TODO(b/199934664): See if we can remove this by providing a default impl. 1113 return false; 1114 } finally { 1115 mHasPendingImmediateCursorAnchorInfoUpdate.set(result && hasImmediate); 1116 mIsCursorAnchorInfoMonitoring.set(result && hasMonitoring); 1117 } 1118 } 1119 1120 @Dispatching(cancellable = true) 1121 @Override requestTextBoundsInfo( InputConnectionCommandHeader header, RectF bounds, @NonNull ResultReceiver resultReceiver)1122 public void requestTextBoundsInfo( 1123 InputConnectionCommandHeader header, RectF bounds, 1124 @NonNull ResultReceiver resultReceiver) { 1125 dispatchWithTracing("requestTextBoundsInfo", () -> { 1126 if (header.mSessionId != mCurrentSessionId.get()) { 1127 resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null); 1128 return; // cancelled 1129 } 1130 InputConnection ic = getInputConnection(); 1131 if (ic == null || mDeactivateRequested.get()) { 1132 Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection"); 1133 resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null); 1134 return; 1135 } 1136 1137 ic.requestTextBoundsInfo( 1138 bounds, 1139 Runnable::run, 1140 (textBoundsInfoResult) -> { 1141 final int resultCode = textBoundsInfoResult.getResultCode(); 1142 final TextBoundsInfo textBoundsInfo = 1143 textBoundsInfoResult.getTextBoundsInfo(); 1144 resultReceiver.send(resultCode, 1145 textBoundsInfo == null ? null : textBoundsInfo.toBundle()); 1146 }); 1147 }); 1148 } 1149 1150 @Dispatching(cancellable = true) 1151 @Override commitContent(InputConnectionCommandHeader header, InputContentInfo inputContentInfo, int flags, Bundle opts, AndroidFuture future )1152 public void commitContent(InputConnectionCommandHeader header, 1153 InputContentInfo inputContentInfo, int flags, Bundle opts, 1154 AndroidFuture future /* T=Boolean */) { 1155 final int imeUid = Binder.getCallingUid(); 1156 dispatchWithTracing("commitContent", future, () -> { 1157 // Check if the originator IME has the right permissions 1158 try { 1159 final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri( 1160 inputContentInfo.getContentUri(), UserHandle.getUserId(imeUid)); 1161 final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId( 1162 inputContentInfo.getContentUri()); 1163 UriGrantsManager.getService().checkGrantUriPermission_ignoreNonSystem(imeUid, null, 1164 contentUriWithoutUserId, Intent.FLAG_GRANT_READ_URI_PERMISSION, 1165 contentUriOwnerUserId); 1166 } catch (Exception e) { 1167 Log.w(TAG, "commitContent with invalid Uri permission from IME:", e); 1168 return false; 1169 } 1170 1171 if (header.mSessionId != mCurrentSessionId.get()) { 1172 return false; // cancelled 1173 } 1174 final InputConnection ic = getInputConnection(); 1175 if (ic == null || mDeactivateRequested.get()) { 1176 Log.w(TAG, "commitContent on inactive InputConnection"); 1177 return false; 1178 } 1179 if (inputContentInfo == null || !inputContentInfo.validate()) { 1180 Log.w(TAG, "commitContent with invalid inputContentInfo=" + inputContentInfo); 1181 return false; 1182 } 1183 try { 1184 return ic.commitContent(inputContentInfo, flags, opts); 1185 } catch (AbstractMethodError ignored) { 1186 // TODO(b/199934664): See if we can remove this by providing a default impl. 1187 return false; 1188 } 1189 }); 1190 } 1191 1192 @Dispatching(cancellable = true) 1193 @Override setImeConsumesInput(InputConnectionCommandHeader header, boolean imeConsumesInput)1194 public void setImeConsumesInput(InputConnectionCommandHeader header, boolean imeConsumesInput) { 1195 dispatchWithTracing("setImeConsumesInput", () -> { 1196 if (header.mSessionId != mCurrentSessionId.get()) { 1197 return; // cancelled 1198 } 1199 InputConnection ic = getInputConnection(); 1200 if (ic == null || mDeactivateRequested.get()) { 1201 Log.w(TAG, "setImeConsumesInput on inactive InputConnection"); 1202 return; 1203 } 1204 ic.setImeConsumesInput(imeConsumesInput); 1205 }); 1206 } 1207 1208 @Dispatching(cancellable = true) 1209 @Override replaceText( InputConnectionCommandHeader header, int start, int end, @NonNull CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)1210 public void replaceText( 1211 InputConnectionCommandHeader header, 1212 int start, 1213 int end, 1214 @NonNull CharSequence text, 1215 int newCursorPosition, 1216 @Nullable TextAttribute textAttribute) { 1217 dispatchWithTracing( 1218 "replaceText", 1219 () -> { 1220 if (header.mSessionId != mCurrentSessionId.get()) { 1221 return; // cancelled 1222 } 1223 InputConnection ic = getInputConnection(); 1224 if (ic == null || mDeactivateRequested.get()) { 1225 Log.w(TAG, "replaceText on inactive InputConnection"); 1226 return; 1227 } 1228 ic.replaceText(start, end, text, newCursorPosition, textAttribute); 1229 }); 1230 } 1231 1232 private final IRemoteAccessibilityInputConnection mAccessibilityInputConnection = 1233 new IRemoteAccessibilityInputConnection.Stub() { 1234 @Dispatching(cancellable = true) 1235 @Override 1236 public void commitText(InputConnectionCommandHeader header, CharSequence text, 1237 int newCursorPosition, @Nullable TextAttribute textAttribute) { 1238 dispatchWithTracing("commitTextFromA11yIme", () -> { 1239 if (header.mSessionId != mCurrentSessionId.get()) { 1240 return; // cancelled 1241 } 1242 InputConnection ic = getInputConnection(); 1243 if (ic == null || mDeactivateRequested.get()) { 1244 Log.w(TAG, "commitText on inactive InputConnection"); 1245 return; 1246 } 1247 // A11yIME's commitText() also triggers finishComposingText() automatically. 1248 ic.beginBatchEdit(); 1249 ic.finishComposingText(); 1250 ic.commitText(text, newCursorPosition, textAttribute); 1251 ic.endBatchEdit(); 1252 }); 1253 } 1254 1255 @Dispatching(cancellable = true) 1256 @Override 1257 public void setSelection(InputConnectionCommandHeader header, int start, int end) { 1258 dispatchWithTracing("setSelectionFromA11yIme", () -> { 1259 if (header.mSessionId != mCurrentSessionId.get()) { 1260 return; // cancelled 1261 } 1262 InputConnection ic = getInputConnection(); 1263 if (ic == null || mDeactivateRequested.get()) { 1264 Log.w(TAG, "setSelection on inactive InputConnection"); 1265 return; 1266 } 1267 ic.setSelection(start, end); 1268 }); 1269 } 1270 1271 @Dispatching(cancellable = true) 1272 @Override 1273 public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength, 1274 int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) { 1275 dispatchWithTracing("getSurroundingTextFromA11yIme", future, () -> { 1276 if (header.mSessionId != mCurrentSessionId.get()) { 1277 return null; // cancelled 1278 } 1279 final InputConnection ic = getInputConnection(); 1280 if (ic == null || mDeactivateRequested.get()) { 1281 Log.w(TAG, "getSurroundingText on inactive InputConnection"); 1282 return null; 1283 } 1284 if (beforeLength < 0) { 1285 Log.i(TAG, "Returning null to getSurroundingText due to an invalid" 1286 + " beforeLength=" + beforeLength); 1287 return null; 1288 } 1289 if (afterLength < 0) { 1290 Log.i(TAG, "Returning null to getSurroundingText due to an invalid" 1291 + " afterLength=" + afterLength); 1292 return null; 1293 } 1294 return ic.getSurroundingText(beforeLength, afterLength, flags); 1295 }, useImeTracing() ? result -> buildGetSurroundingTextProto( 1296 beforeLength, afterLength, flags, result) : null); 1297 } 1298 1299 @Dispatching(cancellable = true) 1300 @Override 1301 public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength, 1302 int afterLength) { 1303 dispatchWithTracing("deleteSurroundingTextFromA11yIme", () -> { 1304 if (header.mSessionId != mCurrentSessionId.get()) { 1305 return; // cancelled 1306 } 1307 InputConnection ic = getInputConnection(); 1308 if (ic == null || mDeactivateRequested.get()) { 1309 Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); 1310 return; 1311 } 1312 ic.deleteSurroundingText(beforeLength, afterLength); 1313 }); 1314 } 1315 1316 @Dispatching(cancellable = true) 1317 @Override 1318 public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) { 1319 dispatchWithTracing("sendKeyEventFromA11yIme", () -> { 1320 if (header.mSessionId != mCurrentSessionId.get()) { 1321 return; // cancelled 1322 } 1323 InputConnection ic = getInputConnection(); 1324 if (ic == null || mDeactivateRequested.get()) { 1325 Log.w(TAG, "sendKeyEvent on inactive InputConnection"); 1326 return; 1327 } 1328 ic.sendKeyEvent(event); 1329 }); 1330 } 1331 1332 @Dispatching(cancellable = true) 1333 @Override 1334 public void performEditorAction(InputConnectionCommandHeader header, int id) { 1335 dispatchWithTracing("performEditorActionFromA11yIme", () -> { 1336 if (header.mSessionId != mCurrentSessionId.get()) { 1337 return; // cancelled 1338 } 1339 InputConnection ic = getInputConnection(); 1340 if (ic == null || mDeactivateRequested.get()) { 1341 Log.w(TAG, "performEditorAction on inactive InputConnection"); 1342 return; 1343 } 1344 ic.performEditorAction(id); 1345 }); 1346 } 1347 1348 @Dispatching(cancellable = true) 1349 @Override 1350 public void performContextMenuAction(InputConnectionCommandHeader header, int id) { 1351 dispatchWithTracing("performContextMenuActionFromA11yIme", () -> { 1352 if (header.mSessionId != mCurrentSessionId.get()) { 1353 return; // cancelled 1354 } 1355 InputConnection ic = getInputConnection(); 1356 if (ic == null || mDeactivateRequested.get()) { 1357 Log.w(TAG, "performContextMenuAction on inactive InputConnection"); 1358 return; 1359 } 1360 ic.performContextMenuAction(id); 1361 }); 1362 } 1363 1364 @Dispatching(cancellable = true) 1365 @Override 1366 public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes, 1367 AndroidFuture future /* T=Integer */) { 1368 dispatchWithTracing("getCursorCapsModeFromA11yIme", future, () -> { 1369 if (header.mSessionId != mCurrentSessionId.get()) { 1370 return 0; // cancelled 1371 } 1372 final InputConnection ic = getInputConnection(); 1373 if (ic == null || mDeactivateRequested.get()) { 1374 Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); 1375 return 0; 1376 } 1377 return ic.getCursorCapsMode(reqModes); 1378 }, useImeTracing() ? result -> buildGetCursorCapsModeProto(reqModes, result) : null); 1379 } 1380 1381 @Dispatching(cancellable = true) 1382 @Override 1383 public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) { 1384 dispatchWithTracing("clearMetaKeyStatesFromA11yIme", () -> { 1385 if (header.mSessionId != mCurrentSessionId.get()) { 1386 return; // cancelled 1387 } 1388 InputConnection ic = getInputConnection(); 1389 if (ic == null || mDeactivateRequested.get()) { 1390 Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); 1391 return; 1392 } 1393 ic.clearMetaKeyStates(states); 1394 }); 1395 } 1396 }; 1397 1398 /** 1399 * @return {@link IRemoteAccessibilityInputConnection} associated with this object. 1400 */ asIRemoteAccessibilityInputConnection()1401 public IRemoteAccessibilityInputConnection asIRemoteAccessibilityInputConnection() { 1402 return mAccessibilityInputConnection; 1403 } 1404 dispatch(@onNull Runnable runnable)1405 private void dispatch(@NonNull Runnable runnable) { 1406 // If we are calling this from the target thread, then we can call right through. 1407 // Otherwise, we need to send the message to the target thread. 1408 if (mLooper.isCurrentThread()) { 1409 runnable.run(); 1410 return; 1411 } 1412 1413 mH.post(runnable); 1414 } 1415 dispatchWithTracing(@onNull String methodName, @NonNull Runnable runnable)1416 private void dispatchWithTracing(@NonNull String methodName, @NonNull Runnable runnable) { 1417 final Runnable actualRunnable; 1418 if (Trace.isTagEnabled(Trace.TRACE_TAG_INPUT)) { 1419 actualRunnable = () -> { 1420 Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#" + methodName); 1421 try { 1422 runnable.run(); 1423 } finally { 1424 Trace.traceEnd(Trace.TRACE_TAG_INPUT); 1425 } 1426 }; 1427 } else { 1428 actualRunnable = runnable; 1429 } 1430 1431 dispatch(actualRunnable); 1432 } 1433 dispatchWithTracing(@onNull String methodName, @NonNull AndroidFuture untypedFuture, @NonNull Supplier<T> supplier)1434 private <T> void dispatchWithTracing(@NonNull String methodName, 1435 @NonNull AndroidFuture untypedFuture, @NonNull Supplier<T> supplier) { 1436 dispatchWithTracing(methodName, untypedFuture, supplier, null /* dumpProtoProvider */); 1437 } 1438 dispatchWithTracing(@onNull String methodName, @NonNull AndroidFuture untypedFuture, @NonNull Supplier<T> supplier, @Nullable Function<T, byte[]> dumpProtoProvider)1439 private <T> void dispatchWithTracing(@NonNull String methodName, 1440 @NonNull AndroidFuture untypedFuture, @NonNull Supplier<T> supplier, 1441 @Nullable Function<T, byte[]> dumpProtoProvider) { 1442 @SuppressWarnings("unchecked") 1443 final AndroidFuture<T> future = untypedFuture; 1444 dispatchWithTracing(methodName, () -> { 1445 final T result; 1446 try { 1447 result = supplier.get(); 1448 } catch (Throwable throwable) { 1449 future.completeExceptionally(throwable); 1450 throw throwable; 1451 } 1452 future.complete(result); 1453 if (dumpProtoProvider != null) { 1454 final byte[] icProto = dumpProtoProvider.apply(result); 1455 ImeTracing.getInstance().triggerClientDump( 1456 TAG + "#" + methodName, mParentInputMethodManager, icProto); 1457 } 1458 }); 1459 } 1460 useImeTracing()1461 private static boolean useImeTracing() { 1462 return ImeTracing.getInstance().isEnabled(); 1463 } 1464 } 1465