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