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