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