• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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