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