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