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