• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.inputmethodservice;
18 
19 import static android.view.inputmethod.TextBoundsInfoResult.CODE_CANCELLED;
20 
21 import android.annotation.AnyThread;
22 import android.annotation.CallbackExecutor;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.graphics.RectF;
26 import android.os.Bundle;
27 import android.os.CancellationSignal;
28 import android.os.CancellationSignalBeamer;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.os.ResultReceiver;
32 import android.view.KeyEvent;
33 import android.view.inputmethod.CompletionInfo;
34 import android.view.inputmethod.CorrectionInfo;
35 import android.view.inputmethod.ExtractedText;
36 import android.view.inputmethod.ExtractedTextRequest;
37 import android.view.inputmethod.HandwritingGesture;
38 import android.view.inputmethod.InputConnection;
39 import android.view.inputmethod.InputContentInfo;
40 import android.view.inputmethod.ParcelableHandwritingGesture;
41 import android.view.inputmethod.SurroundingText;
42 import android.view.inputmethod.TextAttribute;
43 import android.view.inputmethod.TextBoundsInfo;
44 import android.view.inputmethod.TextBoundsInfoResult;
45 
46 import com.android.internal.infra.AndroidFuture;
47 import com.android.internal.inputmethod.IRemoteInputConnection;
48 import com.android.internal.inputmethod.InputConnectionCommandHeader;
49 
50 import java.util.Objects;
51 import java.util.concurrent.Executor;
52 import java.util.function.Consumer;
53 import java.util.function.IntConsumer;
54 
55 /**
56  * A stateless wrapper of {@link com.android.internal.inputmethod.IRemoteInputConnection} to
57  * encapsulate boilerplate code around {@link AndroidFuture} and {@link RemoteException}.
58  */
59 final class IRemoteInputConnectionInvoker {
60 
61     @NonNull
62     private final IRemoteInputConnection mConnection;
63     private final int mSessionId;
64     private CancellationSignalBeamer.Sender mBeamer;
65 
IRemoteInputConnectionInvoker(@onNull IRemoteInputConnection inputConnection, int sessionId)66     private IRemoteInputConnectionInvoker(@NonNull IRemoteInputConnection inputConnection,
67             int sessionId) {
68         mConnection = inputConnection;
69         mSessionId = sessionId;
70     }
71 
72     private abstract static class OnceResultReceiver<C> extends ResultReceiver {
73         @Nullable
74         private C mConsumer;
75         @Nullable
76         private Executor mExecutor;
77 
OnceResultReceiver(@onNull Executor executor, @NonNull C consumer)78         protected OnceResultReceiver(@NonNull Executor executor, @NonNull C consumer) {
79             super(null);
80             Objects.requireNonNull(executor);
81             Objects.requireNonNull(consumer);
82             mExecutor = executor;
83             mConsumer = consumer;
84         }
85 
86         @Override
onReceiveResult(int resultCode, Bundle resultData)87         protected final void onReceiveResult(int resultCode, Bundle resultData) {
88             final Executor executor;
89             final C consumer;
90             synchronized (this) {
91                 executor = mExecutor;
92                 consumer = mConsumer;
93                 mExecutor = null;
94                 mConsumer = null;
95             }
96             if (executor != null && consumer != null) {
97                 dispatch(executor, consumer, resultCode, resultData);
98             }
99         }
100 
dispatch(@onNull Executor executor, @NonNull C consumer, int code, Bundle data)101         protected abstract void dispatch(@NonNull Executor executor, @NonNull C consumer, int code,
102                 Bundle data);
103     }
104 
105     /**
106      * Subclass of {@link ResultReceiver} used by
107      * {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)} for providing
108      * callback.
109      */
110     private static final class IntResultReceiver extends OnceResultReceiver<IntConsumer> {
IntResultReceiver(@onNull Executor executor, @NonNull IntConsumer consumer)111         IntResultReceiver(@NonNull Executor executor, @NonNull IntConsumer consumer) {
112             super(executor, consumer);
113         }
114 
115         @Override
dispatch(@onNull Executor executor, @NonNull IntConsumer consumer, int code, Bundle data)116         protected void dispatch(@NonNull Executor executor, @NonNull IntConsumer consumer, int code,
117                 Bundle data) {
118             executor.execute(() -> consumer.accept(code));
119         }
120     }
121 
122     /**
123      * Subclass of {@link ResultReceiver} used by
124      * {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing
125      * callback.
126      */
127     private static final class TextBoundsInfoResultReceiver extends
128             OnceResultReceiver<Consumer<TextBoundsInfoResult>> {
TextBoundsInfoResultReceiver(@onNull Executor executor, @NonNull Consumer<TextBoundsInfoResult> consumer)129         TextBoundsInfoResultReceiver(@NonNull Executor executor,
130                 @NonNull Consumer<TextBoundsInfoResult> consumer) {
131             super(executor, consumer);
132         }
133 
134         @Override
dispatch(@onNull Executor executor, @NonNull Consumer<TextBoundsInfoResult> consumer, int code, Bundle data)135         protected void dispatch(@NonNull Executor executor,
136                 @NonNull Consumer<TextBoundsInfoResult> consumer, int code, Bundle data) {
137             final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult(
138                     code, TextBoundsInfo.createFromBundle(data));
139             executor.execute(() -> consumer.accept(textBoundsInfoResult));
140         }
141     }
142 
143     /**
144      * Creates a new instance of {@link IRemoteInputConnectionInvoker} for the given
145      * {@link IRemoteInputConnection}.
146      *
147      * @param connection {@link IRemoteInputConnection} to be wrapped.
148      * @return A new instance of {@link IRemoteInputConnectionInvoker}.
149      */
create(@onNull IRemoteInputConnection connection)150     public static IRemoteInputConnectionInvoker create(@NonNull IRemoteInputConnection connection) {
151         Objects.requireNonNull(connection);
152         return new IRemoteInputConnectionInvoker(connection, 0);
153     }
154 
155     /**
156      * Creates a new instance of {@link IRemoteInputConnectionInvoker} with the given
157      * {@code sessionId}.
158      *
159      * @param sessionId the new session ID to be used.
160      * @return A new instance of {@link IRemoteInputConnectionInvoker}.
161      */
162     @NonNull
cloneWithSessionId(int sessionId)163     public IRemoteInputConnectionInvoker cloneWithSessionId(int sessionId) {
164         return new IRemoteInputConnectionInvoker(mConnection, sessionId);
165     }
166 
167     /**
168      * @param connection {@code IRemoteInputConnection} to be compared with
169      * @return {@code true} if the underlying {@code IRemoteInputConnection} is the same.
170      *         {@code false} if {@code inputContext} is {@code null}.
171      */
172     @AnyThread
isSameConnection(@onNull IRemoteInputConnection connection)173     public boolean isSameConnection(@NonNull IRemoteInputConnection connection) {
174         if (connection == null) {
175             return false;
176         }
177         return mConnection.asBinder() == connection.asBinder();
178     }
179 
180     @NonNull
createHeader()181     InputConnectionCommandHeader createHeader() {
182         return new InputConnectionCommandHeader(mSessionId);
183     }
184 
185     /**
186      * Invokes {@link IRemoteInputConnection#getTextAfterCursor(InputConnectionCommandHeader, int,
187      * int, AndroidFuture)}.
188      *
189      * @param length {@code length} parameter to be passed.
190      * @param flags {@code flags} parameter to be passed.
191      * @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation
192      *         result. {@link RemoteException} will be treated as an error.
193      */
194     @AnyThread
195     @NonNull
getTextAfterCursor(int length, int flags)196     public AndroidFuture<CharSequence> getTextAfterCursor(int length, int flags) {
197         final AndroidFuture<CharSequence> future = new AndroidFuture<>();
198         try {
199             mConnection.getTextAfterCursor(createHeader(), length, flags, future);
200         } catch (RemoteException e) {
201             future.completeExceptionally(e);
202         }
203         return future;
204     }
205 
206     /**
207      * Invokes {@link IRemoteInputConnection#getTextBeforeCursor(InputConnectionCommandHeader, int,
208      * int, AndroidFuture)}.
209      *
210      * @param length {@code length} parameter to be passed.
211      * @param flags {@code flags} parameter to be passed.
212      * @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation
213      *         result. {@link RemoteException} will be treated as an error.
214      */
215     @AnyThread
216     @NonNull
getTextBeforeCursor(int length, int flags)217     public AndroidFuture<CharSequence> getTextBeforeCursor(int length, int flags) {
218         final AndroidFuture<CharSequence> future = new AndroidFuture<>();
219         try {
220             mConnection.getTextBeforeCursor(createHeader(), length, flags, future);
221         } catch (RemoteException e) {
222             future.completeExceptionally(e);
223         }
224         return future;
225     }
226 
227     /**
228      * Invokes {@link IRemoteInputConnection#getSelectedText(InputConnectionCommandHeader, int,
229      * AndroidFuture)}.
230      *
231      * @param flags {@code flags} parameter to be passed.
232      * @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation
233      *         result. {@link RemoteException} will be treated as an error.
234      */
235     @AnyThread
236     @NonNull
getSelectedText(int flags)237     public AndroidFuture<CharSequence> getSelectedText(int flags) {
238         final AndroidFuture<CharSequence> future = new AndroidFuture<>();
239         try {
240             mConnection.getSelectedText(createHeader(), flags, future);
241         } catch (RemoteException e) {
242             future.completeExceptionally(e);
243         }
244         return future;
245     }
246 
247     /**
248      * Invokes
249      * {@link IRemoteInputConnection#getSurroundingText(InputConnectionCommandHeader, int, int, int,
250      * AndroidFuture)}.
251      *
252      * @param beforeLength {@code beforeLength} parameter to be passed.
253      * @param afterLength {@code afterLength} parameter to be passed.
254      * @param flags {@code flags} parameter to be passed.
255      * @return {@link AndroidFuture<SurroundingText>} that can be used to retrieve the
256      *         invocation result. {@link RemoteException} will be treated as an error.
257      */
258     @AnyThread
259     @NonNull
getSurroundingText(int beforeLength, int afterLength, int flags)260     public AndroidFuture<SurroundingText> getSurroundingText(int beforeLength, int afterLength,
261             int flags) {
262         final AndroidFuture<SurroundingText> future = new AndroidFuture<>();
263         try {
264             mConnection.getSurroundingText(createHeader(), beforeLength, afterLength, flags,
265                     future);
266         } catch (RemoteException e) {
267             future.completeExceptionally(e);
268         }
269         return future;
270     }
271 
272     /**
273      * Invokes {@link IRemoteInputConnection#getCursorCapsMode(InputConnectionCommandHeader, int,
274      * AndroidFuture)}.
275      *
276      * @param reqModes {@code reqModes} parameter to be passed.
277      * @return {@link AndroidFuture<Integer>} that can be used to retrieve the invocation
278      *         result. {@link RemoteException} will be treated as an error.
279      */
280     @AnyThread
281     @NonNull
getCursorCapsMode(int reqModes)282     public AndroidFuture<Integer> getCursorCapsMode(int reqModes) {
283         final AndroidFuture<Integer> future = new AndroidFuture<>();
284         try {
285             mConnection.getCursorCapsMode(createHeader(), reqModes, future);
286         } catch (RemoteException e) {
287             future.completeExceptionally(e);
288         }
289         return future;
290     }
291 
292     /**
293      * Invokes {@link IRemoteInputConnection#getExtractedText(InputConnectionCommandHeader,
294      * ExtractedTextRequest, int, AndroidFuture)}.
295      *
296      * @param request {@code request} parameter to be passed.
297      * @param flags {@code flags} parameter to be passed.
298      * @return {@link AndroidFuture<ExtractedText>} that can be used to retrieve the invocation
299      *         result. {@link RemoteException} will be treated as an error.
300      */
301     @AnyThread
302     @NonNull
getExtractedText(ExtractedTextRequest request, int flags)303     public AndroidFuture<ExtractedText> getExtractedText(ExtractedTextRequest request,
304             int flags) {
305         final AndroidFuture<ExtractedText> future = new AndroidFuture<>();
306         try {
307             mConnection.getExtractedText(createHeader(), request, flags, future);
308         } catch (RemoteException e) {
309             future.completeExceptionally(e);
310         }
311         return future;
312     }
313 
314     /**
315      * Invokes
316      * {@link IRemoteInputConnection#commitText(InputConnectionCommandHeader, CharSequence, int)}.
317      *
318      * @param text {@code text} parameter to be passed.
319      * @param newCursorPosition {@code newCursorPosition} parameter to be passed.
320      * @return {@code true} if the invocation is completed without {@link RemoteException}.
321      *         {@code false} otherwise.
322      */
323     @AnyThread
commitText(CharSequence text, int newCursorPosition)324     public boolean commitText(CharSequence text, int newCursorPosition) {
325         try {
326             mConnection.commitText(createHeader(), text, newCursorPosition);
327             return true;
328         } catch (RemoteException e) {
329             return false;
330         }
331     }
332 
333     /**
334      * Invokes {@link IRemoteInputConnection#commitTextWithTextAttribute(
335      * InputConnectionCommandHeader, int, CharSequence)}.
336      *
337      * @param text {@code text} parameter to be passed.
338      * @param newCursorPosition {@code newCursorPosition} parameter to be passed.
339      * @param textAttribute The extra information about the text.
340      * @return {@code true} if the invocation is completed without {@link RemoteException}.
341      *         {@code false} otherwise.
342      */
343     @AnyThread
commitText(CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)344     public boolean commitText(CharSequence text, int newCursorPosition,
345             @Nullable TextAttribute textAttribute) {
346         try {
347             mConnection.commitTextWithTextAttribute(
348                     createHeader(), text, newCursorPosition, textAttribute);
349             return true;
350         } catch (RemoteException e) {
351             return false;
352         }
353     }
354 
355     /**
356      * Invokes {@link IRemoteInputConnection#commitCompletion(InputConnectionCommandHeader,
357      * CompletionInfo)}.
358      *
359      * @param text {@code text} parameter to be passed.
360      * @return {@code true} if the invocation is completed without {@link RemoteException}.
361      *         {@code false} otherwise.
362      */
363     @AnyThread
commitCompletion(CompletionInfo text)364     public boolean commitCompletion(CompletionInfo text) {
365         try {
366             mConnection.commitCompletion(createHeader(), text);
367             return true;
368         } catch (RemoteException e) {
369             return false;
370         }
371     }
372 
373     /**
374      * Invokes {@link IRemoteInputConnection#commitCorrection(InputConnectionCommandHeader,
375      * CorrectionInfo)}.
376      *
377      * @param correctionInfo {@code correctionInfo} parameter to be passed.
378      * @return {@code true} if the invocation is completed without {@link RemoteException}.
379      *         {@code false} otherwise.
380      */
381     @AnyThread
commitCorrection(CorrectionInfo correctionInfo)382     public boolean commitCorrection(CorrectionInfo correctionInfo) {
383         try {
384             mConnection.commitCorrection(createHeader(), correctionInfo);
385             return true;
386         } catch (RemoteException e) {
387             return false;
388         }
389     }
390 
391     /**
392      * Invokes {@link IRemoteInputConnection#setSelection(InputConnectionCommandHeader, int, int)}.
393      *
394      * @param start {@code start} parameter to be passed.
395      * @param end {@code start} parameter to be passed.
396      * @return {@code true} if the invocation is completed without {@link RemoteException}.
397      *         {@code false} otherwise.
398      */
399     @AnyThread
setSelection(int start, int end)400     public boolean setSelection(int start, int end) {
401         try {
402             mConnection.setSelection(createHeader(), start, end);
403             return true;
404         } catch (RemoteException e) {
405             return false;
406         }
407     }
408 
409     /**
410      * Invokes
411      * {@link IRemoteInputConnection#performEditorAction(InputConnectionCommandHeader, int)}.
412      *
413      * @param actionCode {@code start} parameter to be passed.
414      * @return {@code true} if the invocation is completed without {@link RemoteException}.
415      *         {@code false} otherwise.
416      */
417     @AnyThread
performEditorAction(int actionCode)418     public boolean performEditorAction(int actionCode) {
419         try {
420             mConnection.performEditorAction(createHeader(), actionCode);
421             return true;
422         } catch (RemoteException e) {
423             return false;
424         }
425     }
426 
427     /**
428      * Invokes
429      * {@link IRemoteInputConnection#performContextMenuAction(InputConnectionCommandHeader, int)}.
430      *
431      * @param id {@code id} parameter to be passed.
432      * @return {@code true} if the invocation is completed without {@link RemoteException}.
433      *         {@code false} otherwise.
434      */
435     @AnyThread
performContextMenuAction(int id)436     public boolean performContextMenuAction(int id) {
437         try {
438             mConnection.performContextMenuAction(createHeader(), id);
439             return true;
440         } catch (RemoteException e) {
441             return false;
442         }
443     }
444 
445     /**
446      * Invokes
447      * {@link IRemoteInputConnection#setComposingRegion(InputConnectionCommandHeader, int, int)}.
448      *
449      * @param start {@code id} parameter to be passed.
450      * @param end {@code id} parameter to be passed.
451      * @return {@code true} if the invocation is completed without {@link RemoteException}.
452      *         {@code false} otherwise.
453      */
454     @AnyThread
setComposingRegion(int start, int end)455     public boolean setComposingRegion(int start, int end) {
456         try {
457             mConnection.setComposingRegion(createHeader(), start, end);
458             return true;
459         } catch (RemoteException e) {
460             return false;
461         }
462     }
463 
464     /**
465      * Invokes {@link IRemoteInputConnection#setComposingRegionWithTextAttribute(
466      * InputConnectionCommandHeader, int, int, TextAttribute)}.
467      *
468      * @param start {@code id} parameter to be passed.
469      * @param end {@code id} parameter to be passed.
470      * @param textAttribute The extra information about the text.
471      * @return {@code true} if the invocation is completed without {@link RemoteException}.
472      *         {@code false} otherwise.
473      */
474     @AnyThread
setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute)475     public boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) {
476         try {
477             mConnection.setComposingRegionWithTextAttribute(
478                     createHeader(), start, end, textAttribute);
479             return true;
480         } catch (RemoteException e) {
481             return false;
482         }
483     }
484 
485     /**
486      * Invokes {@link IRemoteInputConnection#setComposingText(InputConnectionCommandHeader,
487      * CharSequence, int)}.
488      *
489      * @param text {@code text} parameter to be passed.
490      * @param newCursorPosition {@code newCursorPosition} parameter to be passed.
491      * @return {@code true} if the invocation is completed without {@link RemoteException}.
492      *         {@code false} otherwise.
493      */
494     @AnyThread
setComposingText(CharSequence text, int newCursorPosition)495     public boolean setComposingText(CharSequence text, int newCursorPosition) {
496         try {
497             mConnection.setComposingText(createHeader(), text, newCursorPosition);
498             return true;
499         } catch (RemoteException e) {
500             return false;
501         }
502     }
503 
504     /**
505      * Invokes {@link IRemoteInputConnection#setComposingTextWithTextAttribute(
506      * InputConnectionCommandHeader, CharSequence, int, TextAttribute)}.
507      *
508      * @param text {@code text} parameter to be passed.
509      * @param newCursorPosition {@code newCursorPosition} parameter to be passed.
510      * @param textAttribute The extra information about the text.
511      * @return {@code true} if the invocation is completed without {@link RemoteException}.
512      *         {@code false} otherwise.
513      */
514     @AnyThread
setComposingText(CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)515     public boolean setComposingText(CharSequence text, int newCursorPosition,
516             @Nullable TextAttribute textAttribute) {
517         try {
518             mConnection.setComposingTextWithTextAttribute(
519                     createHeader(), text, newCursorPosition, textAttribute);
520             return true;
521         } catch (RemoteException e) {
522             return false;
523         }
524     }
525 
526     /**
527      * Invokes {@link IRemoteInputConnection#finishComposingText(InputConnectionCommandHeader)}.
528      *
529      * @return {@code true} if the invocation is completed without {@link RemoteException}.
530      *         {@code false} otherwise.
531      */
532     @AnyThread
finishComposingText()533     public boolean finishComposingText() {
534         try {
535             mConnection.finishComposingText(createHeader());
536             return true;
537         } catch (RemoteException e) {
538             return false;
539         }
540     }
541 
542     /**
543      * Invokes {@link IRemoteInputConnection#beginBatchEdit(InputConnectionCommandHeader)}.
544      *
545      * @return {@code true} if the invocation is completed without {@link RemoteException}.
546      *         {@code false} otherwise.
547      */
548     @AnyThread
beginBatchEdit()549     public boolean beginBatchEdit() {
550         try {
551             mConnection.beginBatchEdit(createHeader());
552             return true;
553         } catch (RemoteException e) {
554             return false;
555         }
556     }
557 
558     /**
559      * Invokes {@link IRemoteInputConnection#endBatchEdit(InputConnectionCommandHeader)}.
560      *
561      * @return {@code true} if the invocation is completed without {@link RemoteException}.
562      *         {@code false} otherwise.
563      */
564     @AnyThread
endBatchEdit()565     public boolean endBatchEdit() {
566         try {
567             mConnection.endBatchEdit(createHeader());
568             return true;
569         } catch (RemoteException e) {
570             return false;
571         }
572     }
573 
574     /**
575      * Invokes {@link IRemoteInputConnection#sendKeyEvent(InputConnectionCommandHeader, KeyEvent)}.
576      *
577      * @param event {@code event} parameter to be passed.
578      * @return {@code true} if the invocation is completed without {@link RemoteException}.
579      *         {@code false} otherwise.
580      */
581     @AnyThread
sendKeyEvent(KeyEvent event)582     public boolean sendKeyEvent(KeyEvent event) {
583         try {
584             mConnection.sendKeyEvent(createHeader(), event);
585             return true;
586         } catch (RemoteException e) {
587             return false;
588         }
589     }
590 
591     /**
592      * Invokes {@link IRemoteInputConnection#clearMetaKeyStates(InputConnectionCommandHeader, int)}.
593      *
594      * @param states {@code states} parameter to be passed.
595      * @return {@code true} if the invocation is completed without {@link RemoteException}.
596      *         {@code false} otherwise.
597      */
598     @AnyThread
clearMetaKeyStates(int states)599     public boolean clearMetaKeyStates(int states) {
600         try {
601             mConnection.clearMetaKeyStates(createHeader(), states);
602             return true;
603         } catch (RemoteException e) {
604             return false;
605         }
606     }
607 
608     /**
609      * Invokes
610      * {@link IRemoteInputConnection#deleteSurroundingText(InputConnectionCommandHeader, int, int)}.
611      *
612      * @param beforeLength {@code beforeLength} parameter to be passed.
613      * @param afterLength {@code afterLength} parameter to be passed.
614      * @return {@code true} if the invocation is completed without {@link RemoteException}.
615      *         {@code false} otherwise.
616      */
617     @AnyThread
deleteSurroundingText(int beforeLength, int afterLength)618     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
619         try {
620             mConnection.deleteSurroundingText(createHeader(), beforeLength, afterLength);
621             return true;
622         } catch (RemoteException e) {
623             return false;
624         }
625     }
626 
627     /**
628      * Invokes {@link IRemoteInputConnection#deleteSurroundingTextInCodePoints(
629      * InputConnectionCommandHeader, int, int)}.
630      *
631      * @param beforeLength {@code beforeLength} parameter to be passed.
632      * @param afterLength {@code afterLength} parameter to be passed.
633      * @return {@code true} if the invocation is completed without {@link RemoteException}.
634      *         {@code false} otherwise.
635      */
636     @AnyThread
deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)637     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
638         try {
639             mConnection.deleteSurroundingTextInCodePoints(createHeader(), beforeLength,
640                     afterLength);
641             return true;
642         } catch (RemoteException e) {
643             return false;
644         }
645     }
646 
647     /**
648      * Invokes {@link IRemoteInputConnection#performSpellCheck(InputConnectionCommandHeader)}.
649      *
650      * @return {@code true} if the invocation is completed without {@link RemoteException}.
651      *         {@code false} otherwise.
652      */
653     @AnyThread
performSpellCheck()654     public boolean performSpellCheck() {
655         try {
656             mConnection.performSpellCheck(createHeader());
657             return true;
658         } catch (RemoteException e) {
659             return false;
660         }
661     }
662 
663     /**
664      * Invokes {@link IRemoteInputConnection#performPrivateCommand(InputConnectionCommandHeader,
665      * String, Bundle)}.
666      *
667      * @param action {@code action} parameter to be passed.
668      * @param data {@code data} parameter to be passed.
669      * @return {@code true} if the invocation is completed without {@link RemoteException}.
670      *         {@code false} otherwise.
671      */
672     @AnyThread
performPrivateCommand(String action, Bundle data)673     public boolean performPrivateCommand(String action, Bundle data) {
674         try {
675             mConnection.performPrivateCommand(createHeader(), action, data);
676             return true;
677         } catch (RemoteException e) {
678             return false;
679         }
680     }
681 
682     /**
683      * Invokes {@link IRemoteInputConnection#performHandwritingGesture(
684      * InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}.
685      */
686     @AnyThread
performHandwritingGesture(@onNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer)687     public void performHandwritingGesture(@NonNull HandwritingGesture gesture,
688             @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) {
689         ResultReceiver resultReceiver = null;
690         if (consumer != null) {
691             Objects.requireNonNull(executor);
692             resultReceiver = new IntResultReceiver(executor, consumer);
693         }
694         try {
695             try (var ignored = getCancellationSignalBeamer().beamScopeIfNeeded(gesture)) {
696                 mConnection.performHandwritingGesture(createHeader(),
697                         ParcelableHandwritingGesture.of(gesture),
698                         resultReceiver);
699             }
700         } catch (RemoteException e) {
701             if (consumer != null && executor != null) {
702                 executor.execute(() -> consumer.accept(
703                         InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED));
704             }
705         }
706     }
707 
708     /**
709      * Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture(
710      * InputConnectionCommandHeader, HandwritingGesture, IBinder)}
711      */
712     @AnyThread
previewHandwritingGesture( @onNull HandwritingGesture gesture, @Nullable CancellationSignal cancellationSignal)713     public boolean previewHandwritingGesture(
714             @NonNull HandwritingGesture gesture,
715             @Nullable CancellationSignal cancellationSignal) {
716         try {
717             try (var csToken = beam(cancellationSignal)) {
718                 mConnection.previewHandwritingGesture(createHeader(),
719                         ParcelableHandwritingGesture.of(gesture),
720                         csToken);
721             }
722             return true;
723         } catch (RemoteException e) {
724             return false;
725         }
726     }
727 
728     @Nullable
beam(CancellationSignal cs)729     CancellationSignalBeamer.Sender.CloseableToken beam(CancellationSignal cs) {
730         if (cs == null) {
731             return null;
732         }
733         return getCancellationSignalBeamer().beam(cs);
734     }
735 
getCancellationSignalBeamer()736     private CancellationSignalBeamer.Sender getCancellationSignalBeamer() {
737         if (mBeamer != null) {
738             return mBeamer;
739         }
740         mBeamer = new CancellationSignalBeamer.Sender() {
741             @Override
742             public void onCancel(IBinder token) {
743                 try {
744                     mConnection.cancelCancellationSignal(token);
745                 } catch (RemoteException e) {
746                     // Remote process likely died, ignore.
747                 }
748             }
749 
750             @Override
751             public void onForget(IBinder token) {
752                 try {
753                     mConnection.forgetCancellationSignal(token);
754                 } catch (RemoteException e) {
755                     // Remote process likely died, ignore.
756                 }
757             }
758         };
759 
760         return mBeamer;
761     }
762 
763     /**
764      * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int,
765      * int, AndroidFuture)}.
766      *
767      * @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed.
768      * @param imeDisplayId the display ID that is associated with the IME.
769      * @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation
770      *         result. {@link RemoteException} will be treated as an error.
771      */
772     @AnyThread
773     @NonNull
requestCursorUpdates(int cursorUpdateMode, int imeDisplayId)774     public AndroidFuture<Boolean> requestCursorUpdates(int cursorUpdateMode, int imeDisplayId) {
775         final AndroidFuture<Boolean> future = new AndroidFuture<>();
776         try {
777             mConnection.requestCursorUpdates(createHeader(), cursorUpdateMode, imeDisplayId,
778                     future);
779         } catch (RemoteException e) {
780             future.completeExceptionally(e);
781         }
782         return future;
783     }
784 
785     /**
786      * Invokes {@link IRemoteInputConnection#requestCursorUpdatesWithFilter(
787      * InputConnectionCommandHeader, int, int, int, AndroidFuture)}.
788      *
789      * @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed.
790      * @param cursorUpdateFilter {@code cursorUpdateFilter} parameter to be passed.
791      * @param imeDisplayId the display ID that is associated with the IME.
792      * @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation
793      *         result. {@link RemoteException} will be treated as an error.
794      */
795     @AnyThread
796     @NonNull
requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId)797     public AndroidFuture<Boolean> requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter,
798             int imeDisplayId) {
799         final AndroidFuture<Boolean> future = new AndroidFuture<>();
800         try {
801             mConnection.requestCursorUpdatesWithFilter(createHeader(), cursorUpdateMode,
802                     cursorUpdateFilter, imeDisplayId, future);
803         } catch (RemoteException e) {
804             future.completeExceptionally(e);
805         }
806         return future;
807     }
808 
809     /**
810      * Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader,
811      * RectF, ResultReceiver)}
812      * @param bounds {@code rectF} parameter to be passed.
813      * @param executor {@code Executor} parameter to be passed.
814      * @param consumer {@code Consumer} parameter to be passed.
815      */
816     @AnyThread
requestTextBoundsInfo( @onNull RectF bounds, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<TextBoundsInfoResult> consumer)817     public void requestTextBoundsInfo(
818             @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
819             @NonNull Consumer<TextBoundsInfoResult> consumer) {
820         Objects.requireNonNull(executor);
821         Objects.requireNonNull(consumer);
822 
823         final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer);
824         try {
825             mConnection.requestTextBoundsInfo(createHeader(), bounds, resultReceiver);
826         } catch (RemoteException e) {
827             executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED)));
828         }
829     }
830 
831     /**
832      * Invokes {@link IRemoteInputConnection#commitContent(InputConnectionCommandHeader,
833      * InputContentInfo, int, Bundle, AndroidFuture)}.
834      *
835      * @param inputContentInfo {@code inputContentInfo} parameter to be passed.
836      * @param flags {@code flags} parameter to be passed.
837      * @param opts {@code opts} parameter to be passed.
838      * @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation
839      *         result. {@link RemoteException} will be treated as an error.
840      */
841     @AnyThread
842     @NonNull
commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)843     public AndroidFuture<Boolean> commitContent(InputContentInfo inputContentInfo, int flags,
844             Bundle opts) {
845         final AndroidFuture<Boolean> future = new AndroidFuture<>();
846         try {
847             mConnection.commitContent(createHeader(), inputContentInfo, flags, opts, future);
848         } catch (RemoteException e) {
849             future.completeExceptionally(e);
850         }
851         return future;
852     }
853 
854     /**
855      * Invokes
856      * {@link IRemoteInputConnection#setImeConsumesInput(InputConnectionCommandHeader, boolean)}.
857      *
858      * @param imeConsumesInput {@code imeConsumesInput} parameter to be passed.
859      * @return {@code true} if the invocation is completed without {@link RemoteException}.
860      *         {@code false} otherwise.
861      */
862     @AnyThread
setImeConsumesInput(boolean imeConsumesInput)863     public boolean setImeConsumesInput(boolean imeConsumesInput) {
864         try {
865             mConnection.setImeConsumesInput(createHeader(), imeConsumesInput);
866             return true;
867         } catch (RemoteException e) {
868             return false;
869         }
870     }
871 
872     /**
873      * Replaces the specific range in the current input field with suggested text.
874      *
875      * @param start the character index where the replacement should start.
876      * @param end the character index where the replacement should end.
877      * @param newCursorPosition the new cursor position around the text. If > 0, this is relative to
878      *     the end of the text - 1; if <= 0, this is relative to the start of the text. So a value
879      *     of 1 will always advance you to the position after the full text being inserted. Note
880      *     that this means you can't position the cursor within the text.
881      * @param text the text to replace. This may include styles.
882      * @param textAttribute The extra information about the text. This value may be null.
883      * @return {@code true} if the specific range is replaced successfully, {@code false} otherwise.
884      * @see android.view.inputmethod.InputConnection#replaceText(int, int, CharSequence, int,
885      *     TextAttribute)
886      */
887     @AnyThread
replaceText( int start, int end, @NonNull CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)888     public boolean replaceText(
889             int start,
890             int end,
891             @NonNull CharSequence text,
892             int newCursorPosition,
893             @Nullable TextAttribute textAttribute) {
894         try {
895             mConnection.replaceText(
896                     createHeader(), start, end, text, newCursorPosition, textAttribute);
897             return true;
898         } catch (RemoteException e) {
899             return false;
900         }
901     }
902 }
903