1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.mockime;
18 
19 import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
20 
21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
22 
23 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
24 
25 import android.app.ApplicationExitInfo;
26 import android.app.UiAutomation;
27 import android.app.compat.CompatChanges;
28 import android.content.BroadcastReceiver;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.pm.PackageManager;
32 import android.graphics.RectF;
33 import android.os.Bundle;
34 import android.os.CancellationSignal;
35 import android.os.ParcelFileDescriptor;
36 import android.os.RemoteCallback;
37 import android.os.SystemClock;
38 import android.os.UserHandle;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.view.KeyEvent;
42 import android.view.View;
43 import android.view.inputmethod.CompletionInfo;
44 import android.view.inputmethod.CorrectionInfo;
45 import android.view.inputmethod.DeleteGesture;
46 import android.view.inputmethod.DeleteRangeGesture;
47 import android.view.inputmethod.ExtractedTextRequest;
48 import android.view.inputmethod.HandwritingGesture;
49 import android.view.inputmethod.InputConnection;
50 import android.view.inputmethod.InputContentInfo;
51 import android.view.inputmethod.InputMethodInfo;
52 import android.view.inputmethod.InputMethodManager;
53 import android.view.inputmethod.InputMethodSubtype;
54 import android.view.inputmethod.InsertGesture;
55 import android.view.inputmethod.PreviewableHandwritingGesture;
56 import android.view.inputmethod.SelectGesture;
57 import android.view.inputmethod.SelectRangeGesture;
58 import android.view.inputmethod.TextAttribute;
59 
60 import androidx.annotation.AnyThread;
61 import androidx.annotation.GuardedBy;
62 import androidx.annotation.IntRange;
63 import androidx.annotation.NonNull;
64 import androidx.annotation.Nullable;
65 import androidx.annotation.VisibleForTesting;
66 
67 import com.android.compatibility.common.util.PollingCheck;
68 
69 import org.junit.AssumptionViolatedException;
70 
71 import java.io.IOException;
72 import java.util.List;
73 import java.util.concurrent.CountDownLatch;
74 import java.util.concurrent.Executor;
75 import java.util.concurrent.TimeUnit;
76 import java.util.concurrent.atomic.AtomicBoolean;
77 import java.util.function.Consumer;
78 import java.util.function.IntConsumer;
79 
80 /**
81  * Represents an active Mock IME session, which provides basic primitives to write end-to-end tests
82  * for IME APIs.
83  *
84  * <p>To use {@link MockIme} via {@link MockImeSession}, you need to </p>
85  * <p>Public methods are not thread-safe.</p>
86  */
87 public class MockImeSession implements AutoCloseable {
88 
89     private static final String TAG = "MockImeSession";
90 
91     private final String mImeEventActionName =
92             "com.android.cts.mockime.action.IME_EVENT." + SystemClock.elapsedRealtimeNanos();
93 
94     private static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
95 
96     @NonNull
97     private final Context mContext;
98 
99     @NonNull
100     private final UiAutomation mUiAutomation;
101 
102     @NonNull
103     private final AtomicBoolean mActive = new AtomicBoolean(true);
104 
105     @Nullable
106     private AutoCloseable mSettingsClientCloser;
107 
108     @Nullable
109     private SessionChannel mChannel;
110 
111     // Set with System.currentTimeMillis so it can be compatible with
112     // ApplicationExitInfo.getTimestamp()
113     private long mSessionCreateTimestamp;
114 
115     @MockImePackageNames
116     @NonNull
117     private final String mMockImePackageName;
118 
119     @NonNull
120     private final UserHandle mTargetUser;
121 
122     @MockImePackageNames
123     @NonNull
getMockImePackageName()124     public String getMockImePackageName() {
125         return mMockImePackageName;
126     }
127 
128     @NonNull
129     private final String mMockImeSettingsProviderAuthority;
130 
131     private final boolean mSuppressReset;
132 
133     private static final class EventStore {
134         private static final int INITIAL_ARRAY_SIZE = 32;
135 
136         @NonNull
137         public final ImeEvent[] mArray;
138         public int mLength;
139 
EventStore()140         EventStore() {
141             mArray = new ImeEvent[INITIAL_ARRAY_SIZE];
142             mLength = 0;
143         }
144 
EventStore(EventStore src, int newLength)145         EventStore(EventStore src, int newLength) {
146             mArray = new ImeEvent[newLength];
147             mLength = src.mLength;
148             System.arraycopy(src.mArray, 0, mArray, 0, src.mLength);
149         }
150 
add(ImeEvent event)151         public EventStore add(ImeEvent event) {
152             if (mLength + 1 <= mArray.length) {
153                 mArray[mLength] = event;
154                 ++mLength;
155                 return this;
156             } else {
157                 return new EventStore(this, mLength * 2).add(event);
158             }
159         }
160 
takeSnapshot()161         public ImeEventStream.ImeEventArray takeSnapshot() {
162             return new ImeEventStream.ImeEventArray(mArray, mLength);
163         }
164     }
165 
166     private static final class MockImeEventReceiver implements Consumer<Bundle> {
167         private final Object mLock = new Object();
168 
169         @GuardedBy("mLock")
170         @NonNull
171         private EventStore mCurrentEventStore = new EventStore();
172 
173         @Override
accept(Bundle bundle)174         public void accept(Bundle bundle) {
175             synchronized (mLock) {
176                 mCurrentEventStore =
177                         mCurrentEventStore.add(ImeEvent.fromBundle(bundle));
178             }
179         }
180 
takeEventSnapshot()181         public ImeEventStream.ImeEventArray takeEventSnapshot() {
182             synchronized (mLock) {
183                 return mCurrentEventStore.takeSnapshot();
184             }
185         }
186     }
187     private final MockImeEventReceiver mEventReceiver =
188             new MockImeEventReceiver();
189 
190     private final ImeEventStream mEventStream =
191             new ImeEventStream(mEventReceiver::takeEventSnapshot);
192 
executeShellCommand( @onNull UiAutomation uiAutomation, @NonNull String command)193     private static String executeShellCommand(
194             @NonNull UiAutomation uiAutomation, @NonNull String command) throws IOException {
195         Log.d(TAG, "executeShellCommand(): command=" + command);
196         try (ParcelFileDescriptor.AutoCloseInputStream in =
197                      new ParcelFileDescriptor.AutoCloseInputStream(
198                              uiAutomation.executeShellCommand(command))) {
199             final StringBuilder sb = new StringBuilder();
200             final byte[] buffer = new byte[4096];
201             while (true) {
202                 final int numRead = in.read(buffer);
203                 if (numRead <= 0) {
204                     break;
205                 }
206                 sb.append(new String(buffer, 0, numRead));
207             }
208             String result = sb.toString();
209             Log.d(TAG, "executeShellCommand(): result=" + result);
210             return result;
211         }
212     }
213 
executeImeCmd(String cmd, @Nullable String...args)214     private String executeImeCmd(String cmd, @Nullable String...args) throws IOException {
215         StringBuilder fullCmd = new StringBuilder("ime ").append(cmd);
216         fullCmd.append(" --user ").append(mTargetUser.getIdentifier()).append(' ');
217         for (String arg : args) {
218             // Ideally it should check if there's more args, but adding an extra space is fine
219             fullCmd.append(' ').append(arg);
220         }
221         return executeShellCommand(mUiAutomation, fullCmd.toString());
222     }
223 
224     @Nullable
getCurrentInputMethodId()225     private String getCurrentInputMethodId() {
226         final InputMethodInfo imi =
227                 MultiUserUtils.getCurrentInputMethodInfoAsUser(mContext, mUiAutomation,
228                         mTargetUser);
229         final String result = imi != null ? imi.getId() : null;
230         Log.v(TAG, "getCurrentInputMethodId(): returning " + result + " for user "
231                 + mTargetUser.getIdentifier());
232         return result;
233     }
234 
writeMockImeSettings( @onNull String imeEventActionName, @Nullable ImeSettings.Builder imeSettings, @NonNull RemoteCallback channel)235     private void writeMockImeSettings(
236             @NonNull String imeEventActionName,
237             @Nullable ImeSettings.Builder imeSettings,
238             @NonNull RemoteCallback channel) {
239         final var bundle = ImeSettings.serializeToBundle(imeEventActionName, imeSettings, channel);
240         Log.i(TAG, "Writing MockIme settings: session=" + this + " for user="
241                 + mTargetUser.getIdentifier());
242         MultiUserUtils.callContentProvider(mContext, mUiAutomation,
243                 mMockImeSettingsProviderAuthority, "write", null /* arg */, bundle, mTargetUser);
244     }
245 
setAdditionalSubtypes(@ullable InputMethodSubtype[] additionalSubtypes)246     private void setAdditionalSubtypes(@Nullable InputMethodSubtype[] additionalSubtypes) {
247         final Bundle bundle = new Bundle();
248         bundle.putParcelableArray(SettingsProvider.SET_ADDITIONAL_SUBTYPES_KEY, additionalSubtypes);
249         MultiUserUtils.callContentProvider(mContext, mUiAutomation,
250                 mMockImeSettingsProviderAuthority, SettingsProvider.SET_ADDITIONAL_SUBTYPES_COMMAND,
251                 null /* arg */, bundle, mTargetUser);
252     }
253 
getMockImeComponentName()254     private ComponentName getMockImeComponentName() {
255         return new ComponentName(mMockImePackageName, MockIme.class.getName());
256     }
257 
258     /**
259      * @return the IME ID of the {@link MockIme}.
260      * @see android.view.inputmethod.InputMethodInfo#getId()
261      */
getImeId()262     public String getImeId() {
263         return getMockImeComponentName().flattenToShortString();
264     }
265 
MockImeSession(@onNull Context context, @NonNull UiAutomation uiAutomation, @NonNull ImeSettings.Builder imeSettings)266     private MockImeSession(@NonNull Context context, @NonNull UiAutomation uiAutomation,
267             @NonNull ImeSettings.Builder imeSettings) {
268         mContext = context;
269         mUiAutomation = uiAutomation;
270         mMockImePackageName = imeSettings.mMockImePackageName;
271         mTargetUser = imeSettings.mTargetUser;
272         mMockImeSettingsProviderAuthority = mMockImePackageName + ".provider";
273         mSuppressReset = imeSettings.mSuppressResetIme;
274         updateSessionCreateTimestamp();
275     }
276 
getSessionCreateTimestamp()277     public long getSessionCreateTimestamp() {
278         return mSessionCreateTimestamp;
279     }
280 
updateSessionCreateTimestamp()281     private void updateSessionCreateTimestamp() {
282         mSessionCreateTimestamp = System.currentTimeMillis();
283     }
284 
285     @Nullable
getInputMethodInfo()286     public InputMethodInfo getInputMethodInfo() {
287         for (InputMethodInfo imi :
288                 MultiUserUtils.getInputMethodListAsUser(mContext, mUiAutomation, mTargetUser)) {
289             if (TextUtils.equals(getImeId(), imi.getId())) {
290                 return imi;
291             }
292         }
293         return null;
294     }
295 
initialize(@ullable ImeSettings.Builder imeSettings)296     private void initialize(@Nullable ImeSettings.Builder imeSettings) throws Exception {
297         PollingCheck.check("MockIME was not in getInputMethodList() after timeout.", TIMEOUT_MILLIS,
298                 () -> getInputMethodInfo() != null);
299 
300         // Make sure that MockIME is not selected.
301         if (!mSuppressReset
302                 && MultiUserUtils.getInputMethodListAsUser(mContext, mUiAutomation, mTargetUser)
303                 .stream()
304                 .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
305             executeImeCmd("reset");
306         }
307         if (MultiUserUtils.getEnabledInputMethodListAsUser(mContext, mUiAutomation, mTargetUser)
308                 .stream()
309                 .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
310             throw new IllegalStateException();
311         }
312 
313         // Make sure to set up additional subtypes before launching MockIme.
314         InputMethodSubtype[] additionalSubtypes = imeSettings.mAdditionalSubtypes;
315         if (additionalSubtypes == null) {
316             additionalSubtypes = new InputMethodSubtype[0];
317         }
318         if (additionalSubtypes.length > 0) {
319             setAdditionalSubtypes(additionalSubtypes);
320         } else {
321             final InputMethodInfo imi = getInputMethodInfo();
322             if (imi == null) {
323                 throw new IllegalStateException("MockIME was not in getInputMethodList().");
324             }
325             if (imi.getSubtypeCount() != 0) {
326                 // Somehow the previous run failed to remove additional subtypes. Clean them up.
327                 setAdditionalSubtypes(null);
328             }
329         }
330         {
331             final InputMethodInfo imi = getInputMethodInfo();
332             if (imi == null) {
333                 throw new IllegalStateException("MockIME not found while checking subtypes.");
334             }
335             if (imi.getSubtypeCount() != additionalSubtypes.length) {
336                 throw new IllegalStateException("MockIME subtypes were not correctly set.");
337             }
338         }
339 
340         mSettingsClientCloser = MultiUserUtils.acquireUnstableContentProviderClientSession(mContext,
341                 mUiAutomation, mMockImeSettingsProviderAuthority, mTargetUser);
342         var sessionEstablished = new CountDownLatch(1);
343         mChannel = new SessionChannel(sessionEstablished::countDown);
344         mChannel.registerListener(mEventReceiver);
345         writeMockImeSettings(mImeEventActionName, imeSettings, mChannel.takeTransport());
346         sessionEstablished.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
347 
348         String imeId = getImeId();
349         executeImeCmd("enable", imeId);
350         executeImeCmd("set", imeId);
351 
352         PollingCheck.check("Make sure that MockIME becomes available", TIMEOUT_MILLIS,
353                 () -> getImeId().equals(getCurrentInputMethodId()));
354     }
355 
356     @Override
toString()357     public String toString() {
358         return TAG + "{active=" + mActive + "}";
359     }
360 
361     /** @see #create(Context, UiAutomation, ImeSettings.Builder) */
362     @NonNull
create(@onNull Context context)363     public static MockImeSession create(@NonNull Context context) throws Exception {
364         return create(context, getInstrumentation().getUiAutomation(), new ImeSettings.Builder());
365     }
366 
367     /**
368      * Creates a new Mock IME session. During this session, you can receive various events from
369      * {@link MockIme}.
370      *
371      * @param context {@link Context} to be used to receive inter-process events from the
372      *                {@link MockIme} (e.g. via {@link BroadcastReceiver}
373      * @param uiAutomation {@link UiAutomation} object to change the device state that are typically
374      *                     guarded by permissions.
375      * @param imeSettings Key-value pairs to be passed to the {@link MockIme}.
376      * @return A session object, with which you can retrieve event logs from the {@link MockIme} and
377      *         can clean up the session.
378      */
379     @NonNull
create( @onNull Context context, @NonNull UiAutomation uiAutomation, @Nullable ImeSettings.Builder imeSettings)380     public static MockImeSession create(
381             @NonNull Context context,
382             @NonNull UiAutomation uiAutomation,
383             @Nullable ImeSettings.Builder imeSettings) throws Exception {
384         final String unavailabilityReason = getUnavailabilityReason(context);
385         if (unavailabilityReason != null) {
386             throw new AssumptionViolatedException(unavailabilityReason);
387         }
388         final MockImeSession client = new MockImeSession(context, uiAutomation, imeSettings);
389         client.initialize(imeSettings);
390         return client;
391     }
392 
393     /**
394      * Checks if the {@link MockIme} can be used in this device.
395      *
396      * @return {@code null} if it can be used, or message describing why if it cannot.
397      */
398     @Nullable
getUnavailabilityReason(@onNull Context context)399     public static String getUnavailabilityReason(@NonNull Context context) {
400         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
401             return "Device must support installable IMEs that implement InputMethodService API";
402         }
403         return null;
404     }
405 
406     /**
407      * Whether {@link MockIme} enabled a compatibility flag to finish input without fallback
408      * input connection when device interactive state changed. See detailed description in
409      * {@link MockImeSession#setEnabledFinishInputNoFallbackConnection}.
410      *
411      * @return {@code true} if the compatibility flag is enabled.
412      */
isFinishInputNoFallbackConnectionEnabled()413     public boolean isFinishInputNoFallbackConnectionEnabled() {
414         AtomicBoolean result = new AtomicBoolean();
415         runWithShellPermissionIdentity(() ->
416                 result.set(CompatChanges.isChangeEnabled(FINISH_INPUT_NO_FALLBACK_CONNECTION,
417                         mMockImePackageName, mTargetUser)));
418         return result.get();
419     }
420 
421     /**
422      * Checks whether there are any pending IME visibility requests.
423      *
424      * @see InputMethodManager#hasPendingImeVisibilityRequests()
425      *
426      * @return {@code true} iff there are pending IME visibility requests.
427      */
hasPendingImeVisibilityRequests()428     public boolean hasPendingImeVisibilityRequests() {
429         final var imm = mContext.getSystemService(InputMethodManager.class);
430         return runWithShellPermissionIdentity(imm::hasPendingImeVisibilityRequests);
431     }
432 
433     /**
434      * @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
435      *         session is created.
436      */
openEventStream()437     public ImeEventStream openEventStream() {
438         return mEventStream.copy();
439     }
440 
441     /**
442      * Logs the event stream to logcat.
443      */
logEventStream()444     public void logEventStream() {
445         Log.i(TAG, mEventStream.dump());
446     }
447 
448     /**
449      * @return {@code true} until {@link #close()} gets called.
450      */
451     @AnyThread
isActive()452     public boolean isActive() {
453         return mActive.get();
454     }
455 
456     /**
457      * Closes the active session and de-selects {@link MockIme}. Currently which IME will be
458      * selected next is up to the system.
459      */
close()460     public void close() throws Exception {
461         String exitReason = retrieveExitReasonIfMockImeCrashed();
462         if (exitReason != null) {
463             Log.e(TAG, String.format("MockIme process exit reason: {%s}, event stream: {%s}",
464                     exitReason, mEventStream.dump()));
465         }
466 
467         mActive.set(false);
468 
469         if (!mSuppressReset) {
470             executeImeCmd("reset");
471 
472             PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT_MILLIS, () ->
473                     MultiUserUtils
474                             .getEnabledInputMethodListAsUser(mContext, mUiAutomation, mTargetUser)
475                             .stream()
476                             .noneMatch(
477                                     info -> getMockImeComponentName().equals(info.getComponent())));
478         }
479 
480         if (mChannel != null) {
481             mChannel.close();
482         }
483         Log.i(TAG, "Deleting MockIme settings: session=" + this);
484         MultiUserUtils.callContentProvider(mContext, mUiAutomation,
485                 mMockImeSettingsProviderAuthority, "delete", null /* arg */, null /* extras */,
486                 mTargetUser);
487 
488         // Clean up additional subtypes if any.
489         final InputMethodInfo imi = getInputMethodInfo();
490         if (imi != null && imi.getSubtypeCount() != 0) {
491             setAdditionalSubtypes(null);
492         }
493         if (mSettingsClientCloser != null) {
494             mSettingsClientCloser.close();
495             mSettingsClientCloser = null;
496         }
497         updateSessionCreateTimestamp();
498     }
499 
500     @Nullable
retrieveExitReasonIfMockImeCrashed()501     String retrieveExitReasonIfMockImeCrashed() {
502         final ApplicationExitInfo lastExitReason = findLatestMockImeSessionExitInfo();
503         if (lastExitReason == null) {
504             return null;
505         }
506         if (lastExitReason.getTimestamp() <= mSessionCreateTimestamp) {
507             return null;
508         }
509         final StringBuilder err = new StringBuilder();
510         err.append("MockIme crashed and exited with code: ").append(lastExitReason.getReason())
511                 .append("; ");
512         err.append("session create time: ").append(mSessionCreateTimestamp).append("; ");
513         err.append("process exit time: ").append(lastExitReason.getTimestamp()).append("; ");
514         err.append("see android.app.ApplicationExitInfo for more info on the exit code ");
515         final String exitDescription = lastExitReason.getDescription();
516         if (exitDescription != null) {
517             err.append("(exit Description: ").append(exitDescription).append(")");
518         }
519         return err.toString();
520     }
521 
522     @Nullable
523     @VisibleForTesting
findLatestMockImeSessionExitInfo()524     ApplicationExitInfo findLatestMockImeSessionExitInfo() {
525         final List<ApplicationExitInfo> latestExitReasons =
526                 MultiUserUtils.getHistoricalProcessExitReasons(mContext, mUiAutomation,
527                         mMockImePackageName, /* pid= */ 0, /* maxNum= */ 1, mTargetUser);
528         return latestExitReasons.isEmpty() ? null : latestExitReasons.get(0);
529     }
530 
531     /**
532      * Common logic to send a special command to {@link MockIme}.
533      *
534      * @param commandName command to be passed to {@link MockIme}
535      * @param params {@link Bundle} to be passed to {@link MockIme} as a parameter set of
536      *               {@code commandName}
537      * @return {@link ImeCommand} that is sent to {@link MockIme}.  It can be passed to
538      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
539      *         wait until this event is handled by {@link MockIme}.
540      */
541     @NonNull
callCommandInternal(@onNull String commandName, @NonNull Bundle params)542     private ImeCommand callCommandInternal(@NonNull String commandName, @NonNull Bundle params) {
543         final ImeCommand command = new ImeCommand(
544                 commandName, SystemClock.elapsedRealtimeNanos(), true, params);
545         if (!mChannel.send(command.toBundle())) {
546             throw new IllegalStateException("Channel already closed: " + commandName);
547         }
548         return command;
549     }
550 
551     /**
552      * Lets {@link MockIme} suspend {@link MockIme.AbstractInputMethodImpl#createSession(
553      * android.view.inputmethod.InputMethod.SessionCallback)} until {@link #resumeCreateSession()}.
554      *
555      * <p>This is useful to test a tricky timing issue that the IME client initiated the
556      * IME session but {@link android.view.inputmethod.InputMethodSession} is not available
557      * yet.</p>
558      *
559      * <p>For simplicity and stability, {@link #suspendCreateSession()} must be called before
560      * {@link MockIme.AbstractInputMethodImpl#createSession(
561      * android.view.inputmethod.InputMethod.SessionCallback)} gets called again.</p>
562      *
563      * @return {@link ImeCommand} object that can be passed to
564      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
565      *         wait until this event is handled by {@link MockIme}.
566      */
567     @NonNull
suspendCreateSession()568     public ImeCommand suspendCreateSession() {
569         return callCommandInternal("suspendCreateSession", new Bundle());
570     }
571 
572     /**
573      * Lets {@link MockIme} resume suspended {@link MockIme.AbstractInputMethodImpl#createSession(
574      * android.view.inputmethod.InputMethod.SessionCallback)}.
575      *
576      * <p>Does nothing if {@link #suspendCreateSession()} was not called.</p>
577      *
578      * @return {@link ImeCommand} object that can be passed to
579      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
580      *         wait until this event is handled by {@link MockIme}.
581      */
582     @NonNull
resumeCreateSession()583     public ImeCommand resumeCreateSession() {
584         return callCommandInternal("resumeCreateSession", new Bundle());
585     }
586 
587 
588     /**
589      * Lets {@link MockIme} to call
590      * {@link android.inputmethodservice.InputMethodService#getCurrentInputConnection()} and
591      * memorize  it for later {@link InputConnection}-related operations.
592      *
593      * <p>Only the last one will be memorized if this method gets called multiple times.</p>
594      *
595      * @return {@link ImeCommand} object that can be passed to
596      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
597      *         wait until this event is handled by {@link MockIme}.
598      * @see #unmemorizeCurrentInputConnection()
599      */
600     @NonNull
memorizeCurrentInputConnection()601     public ImeCommand memorizeCurrentInputConnection() {
602         final Bundle params = new Bundle();
603         return callCommandInternal("memorizeCurrentInputConnection", params);
604     }
605 
606     /**
607      * Lets {@link MockIme} to forget memorized {@link InputConnection} if any. Does nothing
608      * otherwise.
609      *
610      * @return {@link ImeCommand} object that can be passed to
611      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
612      *         wait until this event is handled by {@link MockIme}.
613      * @see #memorizeCurrentInputConnection()
614      */
615     @NonNull
unmemorizeCurrentInputConnection()616     public ImeCommand unmemorizeCurrentInputConnection() {
617         final Bundle params = new Bundle();
618         return callCommandInternal("unmemorizeCurrentInputConnection", params);
619     }
620 
621     /**
622      * Lets {@link MockIme} to call {@link InputConnection#getTextBeforeCursor(int, int)} with the
623      * given parameters.
624      *
625      * <p>This triggers {@code getCurrentInputConnection().getTextBeforeCursor(n, flag)}.</p>
626      *
627      * <p>Use {@link ImeEvent#getReturnCharSequenceValue()} for {@link ImeEvent} returned from
628      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
629      * value returned from the API.</p>
630      *
631      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
632      *
633      * @param n to be passed as the {@code n} parameter.
634      * @param flag to be passed as the {@code flag} parameter.
635      * @return {@link ImeCommand} object that can be passed to
636      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
637      *         wait until this event is handled by {@link MockIme}.
638      */
639     @NonNull
callGetTextBeforeCursor(int n, int flag)640     public ImeCommand callGetTextBeforeCursor(int n, int flag) {
641         final Bundle params = new Bundle();
642         params.putInt("n", n);
643         params.putInt("flag", flag);
644         return callCommandInternal("getTextBeforeCursor", params);
645     }
646 
647     /**
648      * Lets {@link MockIme} to call {@link InputConnection#getTextAfterCursor(int, int)} with the
649      * given parameters.
650      *
651      * <p>This triggers {@code getCurrentInputConnection().getTextAfterCursor(n, flag)}.</p>
652      *
653      * <p>Use {@link ImeEvent#getReturnCharSequenceValue()} for {@link ImeEvent} returned from
654      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
655      * value returned from the API.</p>
656      *
657      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
658      *
659      * @param n to be passed as the {@code n} parameter.
660      * @param flag to be passed as the {@code flag} parameter.
661      * @return {@link ImeCommand} object that can be passed to
662      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
663      *         wait until this event is handled by {@link MockIme}.
664      */
665     @NonNull
callGetTextAfterCursor(int n, int flag)666     public ImeCommand callGetTextAfterCursor(int n, int flag) {
667         final Bundle params = new Bundle();
668         params.putInt("n", n);
669         params.putInt("flag", flag);
670         return callCommandInternal("getTextAfterCursor", params);
671     }
672 
673     /**
674      * Lets {@link MockIme} to call {@link InputConnection#getSelectedText(int)} with the
675      * given parameters.
676      *
677      * <p>This triggers {@code getCurrentInputConnection().getSelectedText(flag)}.</p>
678      *
679      * <p>Use {@link ImeEvent#getReturnCharSequenceValue()} for {@link ImeEvent} returned from
680      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
681      * value returned from the API.</p>
682      *
683      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
684      *
685      * @param flag to be passed as the {@code flag} parameter.
686      * @return {@link ImeCommand} object that can be passed to
687      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
688      *         wait until this event is handled by {@link MockIme}.
689      */
690     @NonNull
callGetSelectedText(int flag)691     public ImeCommand callGetSelectedText(int flag) {
692         final Bundle params = new Bundle();
693         params.putInt("flag", flag);
694         return callCommandInternal("getSelectedText", params);
695     }
696 
697     /**
698      * Lets {@link MockIme} to call {@link InputConnection#getSurroundingText(int, int, int)} with
699      * the given parameters.
700      *
701      * <p>This triggers {@code getCurrentInputConnection().getSurroundingText(int, int, int)}.</p>
702      *
703      * <p>Use {@link ImeEvent#getReturnParcelableValue()} for {@link ImeEvent} returned from
704      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
705      * value returned from the API.</p>
706      *
707      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
708      *
709      * @param beforeLength The expected length of the text before the cursor.
710      * @param afterLength The expected length of the text after the cursor.
711      * @param flags Supplies additional options controlling how the text is returned. May be either
712      *              {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}.
713      * @return {@link ImeCommand} object that can be passed to
714      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
715      *         wait until this event is handled by {@link MockIme}.
716      */
717     @NonNull
callGetSurroundingText(@ntRangefrom = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags)718     public ImeCommand callGetSurroundingText(@IntRange(from = 0) int beforeLength,
719             @IntRange(from = 0) int afterLength, int flags) {
720         final Bundle params = new Bundle();
721         params.putInt("beforeLength", beforeLength);
722         params.putInt("afterLength", afterLength);
723         params.putInt("flags", flags);
724         return callCommandInternal("getSurroundingText", params);
725     }
726 
727     /**
728      * Lets {@link MockIme} to call {@link InputConnection#getCursorCapsMode(int)} with the given
729      * parameters.
730      *
731      * <p>This triggers {@code getCurrentInputConnection().getCursorCapsMode(reqModes)}.</p>
732      *
733      * <p>Use {@link ImeEvent#getReturnIntegerValue()} for {@link ImeEvent} returned from
734      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
735      * value returned from the API.</p>
736      *
737      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
738      *
739      * @param reqModes to be passed as the {@code reqModes} parameter.
740      * @return {@link ImeCommand} object that can be passed to
741      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
742      *         wait until this event is handled by {@link MockIme}.
743      */
744     @NonNull
callGetCursorCapsMode(int reqModes)745     public ImeCommand callGetCursorCapsMode(int reqModes) {
746         final Bundle params = new Bundle();
747         params.putInt("reqModes", reqModes);
748         return callCommandInternal("getCursorCapsMode", params);
749     }
750 
751     /**
752      * Lets {@link MockIme} to call
753      * {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} with the given
754      * parameters.
755      *
756      * <p>This triggers {@code getCurrentInputConnection().getExtractedText(request, flags)}.</p>
757      *
758      * <p>Use {@link ImeEvent#getReturnParcelableValue()} for {@link ImeEvent} returned from
759      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
760      * value returned from the API.</p>
761      *
762      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
763      *
764      * @param request to be passed as the {@code request} parameter
765      * @param flags to be passed as the {@code flags} parameter
766      * @return {@link ImeCommand} object that can be passed to
767      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
768      *         wait until this event is handled by {@link MockIme}.
769      */
770     @NonNull
callGetExtractedText(@ullable ExtractedTextRequest request, int flags)771     public ImeCommand callGetExtractedText(@Nullable ExtractedTextRequest request, int flags) {
772         final Bundle params = new Bundle();
773         params.putParcelable("request", request);
774         params.putInt("flags", flags);
775         return callCommandInternal("getExtractedText", params);
776     }
777 
778     /**
779      * Lets {@link MockIme} to call {@link InputConnection#deleteSurroundingText(int, int)} with the
780      * given parameters.
781      *
782      * <p>This triggers
783      * {@code getCurrentInputConnection().deleteSurroundingText(beforeLength, afterLength)}.</p>
784      *
785      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
786      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
787      * value returned from the API.</p>
788      *
789      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
790      *
791      * @param beforeLength to be passed as the {@code beforeLength} parameter
792      * @param afterLength to be passed as the {@code afterLength} parameter
793      * @return {@link ImeCommand} object that can be passed to
794      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
795      *         wait until this event is handled by {@link MockIme}.
796      */
797     @NonNull
callDeleteSurroundingText(int beforeLength, int afterLength)798     public ImeCommand callDeleteSurroundingText(int beforeLength, int afterLength) {
799         final Bundle params = new Bundle();
800         params.putInt("beforeLength", beforeLength);
801         params.putInt("afterLength", afterLength);
802         return callCommandInternal("deleteSurroundingText", params);
803     }
804 
805     /**
806      * Lets {@link MockIme} to call
807      * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)} with the given
808      * parameters.
809      *
810      * <p>This triggers {@code getCurrentInputConnection().deleteSurroundingTextInCodePoints(
811      * beforeLength, afterLength)}.</p>
812      *
813      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
814      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
815      * value returned from the API.</p>
816      *
817      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
818      *
819      * @param beforeLength to be passed as the {@code beforeLength} parameter
820      * @param afterLength to be passed as the {@code afterLength} parameter
821      * @return {@link ImeCommand} object that can be passed to
822      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
823      *         wait until this event is handled by {@link MockIme}.
824      */
825     @NonNull
callDeleteSurroundingTextInCodePoints(int beforeLength, int afterLength)826     public ImeCommand callDeleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
827         final Bundle params = new Bundle();
828         params.putInt("beforeLength", beforeLength);
829         params.putInt("afterLength", afterLength);
830         return callCommandInternal("deleteSurroundingTextInCodePoints", params);
831     }
832 
833     /**
834      * Lets {@link MockIme} to call {@link InputConnection#setComposingText(CharSequence, int)} with
835      * the given parameters.
836      *
837      * <p>This triggers
838      * {@code getCurrentInputConnection().setComposingText(text, newCursorPosition)}.</p>
839      *
840      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
841      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
842      * value returned from the API.</p>
843      *
844      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
845      *
846      * @param text to be passed as the {@code text} parameter
847      * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
848      * @return {@link ImeCommand} object that can be passed to
849      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
850      *         wait until this event is handled by {@link MockIme}.
851      */
852     @NonNull
callSetComposingText(@ullable CharSequence text, int newCursorPosition)853     public ImeCommand callSetComposingText(@Nullable CharSequence text, int newCursorPosition) {
854         final Bundle params = new Bundle();
855         params.putCharSequence("text", text);
856         params.putInt("newCursorPosition", newCursorPosition);
857         return callCommandInternal("setComposingText(CharSequence,int)", params);
858     }
859 
860     /**
861      * Lets {@link MockIme} to call
862      * {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)} with the given
863      * parameters.
864      *
865      * <p>This triggers
866      * {@code getCurrentInputConnection().setComposingText(text, newCursorPosition, textAttribute)}.
867      * </p>
868      *
869      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
870      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
871      * value returned from the API.</p>
872      *
873      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
874      *
875      * @param text to be passed as the {@code text} parameter
876      * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
877      * @param textAttribute to be passed as the {@code textAttribute} parameter
878      * @return {@link ImeCommand} object that can be passed to
879      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
880      *         wait until this event is handled by {@link MockIme}
881      */
882     @NonNull
callSetComposingText(@ullable CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)883     public ImeCommand callSetComposingText(@Nullable CharSequence text, int newCursorPosition,
884             @Nullable TextAttribute textAttribute) {
885         final Bundle params = new Bundle();
886         params.putCharSequence("text", text);
887         params.putInt("newCursorPosition", newCursorPosition);
888         params.putParcelable("textAttribute", textAttribute);
889         return callCommandInternal("setComposingText(CharSequence,int,TextAttribute)", params);
890     }
891 
892     /**
893      * Lets {@link MockIme} to call {@link InputConnection#setComposingRegion(int, int)} with the
894      * given parameters.
895      *
896      * <p>This triggers {@code getCurrentInputConnection().setComposingRegion(start, end)}.</p>
897      *
898      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
899      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
900      * value returned from the API.</p>
901      *
902      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
903      *
904      * @param start to be passed as the {@code start} parameter
905      * @param end to be passed as the {@code end} parameter
906      * @return {@link ImeCommand} object that can be passed to
907      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
908      *         wait until this event is handled by {@link MockIme}.
909      */
910     @NonNull
callSetComposingRegion(int start, int end)911     public ImeCommand callSetComposingRegion(int start, int end) {
912         final Bundle params = new Bundle();
913         params.putInt("start", start);
914         params.putInt("end", end);
915         return callCommandInternal("setComposingRegion(int,int)", params);
916     }
917 
918     /**
919      * Lets {@link MockIme} to call
920      * {@link InputConnection#setComposingRegion(int, int, TextAttribute)} with the given
921      * parameters.
922      *
923      * <p>This triggers
924      * {@code getCurrentInputConnection().setComposingRegion(start, end, TextAttribute)}.</p>
925      *
926      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
927      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
928      * value returned from the API.</p>
929      *
930      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
931      *
932      * @param start to be passed as the {@code start} parameter
933      * @param end to be passed as the {@code end} parameter
934      * @return {@link ImeCommand} object that can be passed to
935      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
936      *         wait until this event is handled by {@link MockIme}.
937      */
938     @NonNull
callSetComposingRegion(int start, int end, @Nullable TextAttribute textAttribute)939     public ImeCommand callSetComposingRegion(int start, int end,
940             @Nullable TextAttribute textAttribute) {
941         final Bundle params = new Bundle();
942         params.putInt("start", start);
943         params.putInt("end", end);
944         params.putParcelable("textAttribute", textAttribute);
945         return callCommandInternal("setComposingRegion(int,int,TextAttribute)", params);
946     }
947 
948     /**
949      * Lets {@link MockIme} to call {@link InputConnection#finishComposingText()} with the given
950      * parameters.
951      *
952      * <p>This triggers {@code getCurrentInputConnection().finishComposingText()}.</p>
953      *
954      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
955      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
956      * value returned from the API.</p>
957      *
958      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
959      *
960      * @return {@link ImeCommand} object that can be passed to
961      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
962      *         wait until this event is handled by {@link MockIme}.
963      */
964     @NonNull
callFinishComposingText()965     public ImeCommand callFinishComposingText() {
966         final Bundle params = new Bundle();
967         return callCommandInternal("finishComposingText", params);
968     }
969 
970     /**
971      * Lets {@link MockIme} to call {@link InputConnection#commitText(CharSequence, int)} with the
972      * given parameters.
973      *
974      * <p>This triggers {@code getCurrentInputConnection().commitText(text, newCursorPosition)}.</p>
975      *
976      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
977      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
978      * value returned from the API.</p>
979      *
980      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
981      *
982      * @param text to be passed as the {@code text} parameter
983      * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
984      * @return {@link ImeCommand} object that can be passed to
985      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
986      *         wait until this event is handled by {@link MockIme}
987      */
988     @NonNull
callCommitText(@ullable CharSequence text, int newCursorPosition)989     public ImeCommand callCommitText(@Nullable CharSequence text, int newCursorPosition) {
990         final Bundle params = new Bundle();
991         params.putCharSequence("text", text);
992         params.putInt("newCursorPosition", newCursorPosition);
993         return callCommandInternal("commitText(CharSequence,int)", params);
994     }
995 
996     /**
997      * Lets {@link MockIme} to call
998      * {@link InputConnection#commitText(CharSequence, int, TextAttribute)} with the given
999      * parameters.
1000      *
1001      * <p>This triggers
1002      * {@code getCurrentInputConnection().commitText(text, newCursorPosition, TextAttribute)}.</p>
1003      *
1004      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1005      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1006      * value returned from the API.</p>
1007      *
1008      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1009      *
1010      * @param text to be passed as the {@code text} parameter
1011      * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
1012      * @return {@link ImeCommand} object that can be passed to
1013      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1014      *         wait until this event is handled by {@link MockIme}
1015      */
1016     @NonNull
callCommitText(@ullable CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)1017     public ImeCommand callCommitText(@Nullable CharSequence text, int newCursorPosition,
1018             @Nullable TextAttribute textAttribute) {
1019         final Bundle params = new Bundle();
1020         params.putCharSequence("text", text);
1021         params.putInt("newCursorPosition", newCursorPosition);
1022         params.putParcelable("textAttribute", textAttribute);
1023         return callCommandInternal("commitText(CharSequence,int,TextAttribute)", params);
1024     }
1025 
1026     /**
1027      * Lets {@link MockIme} to call {@link InputConnection#commitCompletion(CompletionInfo)} with
1028      * the given parameters.
1029      *
1030      * <p>This triggers {@code getCurrentInputConnection().commitCompletion(text)}.</p>
1031      *
1032      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1033      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1034      * value returned from the API.</p>
1035      *
1036      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1037      *
1038      * @param text to be passed as the {@code text} parameter
1039      * @return {@link ImeCommand} object that can be passed to
1040      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1041      *         wait until this event is handled by {@link MockIme}
1042      */
1043     @NonNull
callCommitCompletion(@ullable CompletionInfo text)1044     public ImeCommand callCommitCompletion(@Nullable CompletionInfo text) {
1045         final Bundle params = new Bundle();
1046         params.putParcelable("text", text);
1047         return callCommandInternal("commitCompletion", params);
1048     }
1049 
1050     /**
1051      * Lets {@link MockIme} to call {@link InputConnection#commitCorrection(CorrectionInfo)} with
1052      * the given parameters.
1053      *
1054      * <p>This triggers {@code getCurrentInputConnection().commitCorrection(correctionInfo)}.</p>
1055      *
1056      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1057      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1058      * value returned from the API.</p>
1059      *
1060      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1061      *
1062      * @param correctionInfo to be passed as the {@code correctionInfo} parameter
1063      * @return {@link ImeCommand} object that can be passed to
1064      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1065      *         wait until this event is handled by {@link MockIme}
1066      */
1067     @NonNull
callCommitCorrection(@ullable CorrectionInfo correctionInfo)1068     public ImeCommand callCommitCorrection(@Nullable CorrectionInfo correctionInfo) {
1069         final Bundle params = new Bundle();
1070         params.putParcelable("correctionInfo", correctionInfo);
1071         return callCommandInternal("commitCorrection", params);
1072     }
1073 
1074     /**
1075      * Lets {@link MockIme} to call {@link InputConnection#setSelection(int, int)} with the given
1076      * parameters.
1077      *
1078      * <p>This triggers {@code getCurrentInputConnection().setSelection(start, end)}.</p>
1079      *
1080      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1081      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1082      * value returned from the API.</p>
1083      *
1084      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1085      *
1086      * @param start to be passed as the {@code start} parameter
1087      * @param end to be passed as the {@code end} parameter
1088      * @return {@link ImeCommand} object that can be passed to
1089      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1090      *         wait until this event is handled by {@link MockIme}
1091      */
1092     @NonNull
callSetSelection(int start, int end)1093     public ImeCommand callSetSelection(int start, int end) {
1094         final Bundle params = new Bundle();
1095         params.putInt("start", start);
1096         params.putInt("end", end);
1097         return callCommandInternal("setSelection", params);
1098     }
1099 
1100     /**
1101      * Lets {@link MockIme} to call {@link InputConnection#performEditorAction(int)} with the given
1102      * parameters.
1103      *
1104      * <p>This triggers {@code getCurrentInputConnection().performEditorAction(editorAction)}.</p>
1105      *
1106      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1107      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1108      * value returned from the API.</p>
1109      *
1110      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1111      *
1112      * @param editorAction to be passed as the {@code editorAction} parameter
1113      * @return {@link ImeCommand} object that can be passed to
1114      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1115      *         wait until this event is handled by {@link MockIme}
1116      */
1117     @NonNull
callPerformEditorAction(int editorAction)1118     public ImeCommand callPerformEditorAction(int editorAction) {
1119         final Bundle params = new Bundle();
1120         params.putInt("editorAction", editorAction);
1121         return callCommandInternal("performEditorAction", params);
1122     }
1123 
1124     /**
1125      * Lets {@link MockIme} to call {@link InputConnection#performContextMenuAction(int)} with the
1126      * given parameters.
1127      *
1128      * <p>This triggers {@code getCurrentInputConnection().performContextMenuAction(id)}.</p>
1129      *
1130      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1131      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1132      * value returned from the API.</p>
1133      *
1134      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1135      *
1136      * @param id to be passed as the {@code id} parameter
1137      * @return {@link ImeCommand} object that can be passed to
1138      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1139      *         wait until this event is handled by {@link MockIme}
1140      */
1141     @NonNull
callPerformContextMenuAction(int id)1142     public ImeCommand callPerformContextMenuAction(int id) {
1143         final Bundle params = new Bundle();
1144         params.putInt("id", id);
1145         return callCommandInternal("performContextMenuAction", params);
1146     }
1147 
1148     /**
1149      * Lets {@link MockIme} to call {@link InputConnection#beginBatchEdit()} with the given
1150      * parameters.
1151      *
1152      * <p>This triggers {@code getCurrentInputConnection().beginBatchEdit()}.</p>
1153      *
1154      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1155      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1156      * value returned from the API.</p>
1157      *
1158      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1159      *
1160      * @return {@link ImeCommand} object that can be passed to
1161      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1162      *         wait until this event is handled by {@link MockIme}
1163      */
1164     @NonNull
callBeginBatchEdit()1165     public ImeCommand callBeginBatchEdit() {
1166         final Bundle params = new Bundle();
1167         return callCommandInternal("beginBatchEdit", params);
1168     }
1169 
1170     /**
1171      * Lets {@link MockIme} to call {@link InputConnection#endBatchEdit()} with the given
1172      * parameters.
1173      *
1174      * <p>This triggers {@code getCurrentInputConnection().endBatchEdit()}.</p>
1175      *
1176      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1177      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1178      * value returned from the API.</p>
1179      *
1180      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1181      *
1182      * @return {@link ImeCommand} object that can be passed to
1183      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1184      *         wait until this event is handled by {@link MockIme}
1185      */
1186     @NonNull
callEndBatchEdit()1187     public ImeCommand callEndBatchEdit() {
1188         final Bundle params = new Bundle();
1189         return callCommandInternal("endBatchEdit", params);
1190     }
1191 
1192     /**
1193      * Lets {@link MockIme} to call {@link InputConnection#sendKeyEvent(KeyEvent)} with the given
1194      * parameters.
1195      *
1196      * <p>This triggers {@code getCurrentInputConnection().sendKeyEvent(event)}.</p>
1197      *
1198      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1199      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1200      * value returned from the API.</p>
1201      *
1202      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1203      *
1204      * @param event to be passed as the {@code event} parameter
1205      * @return {@link ImeCommand} object that can be passed to
1206      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1207      *         wait until this event is handled by {@link MockIme}
1208      */
1209     @NonNull
callSendKeyEvent(@ullable KeyEvent event)1210     public ImeCommand callSendKeyEvent(@Nullable KeyEvent event) {
1211         final Bundle params = new Bundle();
1212         params.putParcelable("event", event);
1213         return callCommandInternal("sendKeyEvent", params);
1214     }
1215 
1216     /**
1217      * Lets {@link MockIme} to call {@link InputConnection#performSpellCheck()}.
1218      *
1219      * <p>This triggers {@code getCurrentInputConnection().performSpellCheck()}.</p>
1220      *
1221      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1222 
1223      * @return {@link ImeCommand} object that can be passed to
1224      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1225      *         wait until this event is handled by {@link MockIme}
1226      */
1227     @NonNull
callPerformSpellCheck()1228     public ImeCommand callPerformSpellCheck() {
1229         return callCommandInternal("performSpellCheck", new Bundle());
1230     }
1231 
1232     /**
1233      * Lets {@link MockIme} to call {@link InputConnection#takeSnapshot()}.
1234      *
1235      * <p>This triggers {@code getCurrentInputConnection().takeSnapshot()}.</p>
1236      *
1237      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1238 
1239      * @return {@link ImeCommand} object that can be passed to
1240      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1241      *         wait until this event is handled by {@link MockIme}
1242      */
1243     @NonNull
callTakeSnapshot()1244     public ImeCommand callTakeSnapshot() {
1245         return callCommandInternal("takeSnapshot", new Bundle());
1246     }
1247 
1248     /**
1249      * Lets {@link MockIme} to call {@link InputConnection#clearMetaKeyStates(int)} with the given
1250      * parameters.
1251      *
1252      * <p>This triggers {@code getCurrentInputConnection().sendKeyEvent(event)}.</p>
1253      *
1254      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1255      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1256      * value returned from the API.</p>
1257      *
1258      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1259      *
1260      * @param states to be passed as the {@code states} parameter
1261      * @return {@link ImeCommand} object that can be passed to
1262      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1263      *         wait until this event is handled by {@link MockIme}
1264      */
1265     @NonNull
callClearMetaKeyStates(int states)1266     public ImeCommand callClearMetaKeyStates(int states) {
1267         final Bundle params = new Bundle();
1268         params.putInt("states", states);
1269         return callCommandInternal("clearMetaKeyStates", params);
1270     }
1271 
1272     /**
1273      * Lets {@link MockIme} to call {@link InputConnection#reportFullscreenMode(boolean)} with the
1274      * given parameters.
1275      *
1276      * <p>This triggers {@code getCurrentInputConnection().reportFullscreenMode(enabled)}.</p>
1277      *
1278      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1279      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1280      * value returned from the API.</p>
1281      *
1282      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1283      *
1284      * @param enabled to be passed as the {@code enabled} parameter
1285      * @return {@link ImeCommand} object that can be passed to
1286      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1287      *         wait until this event is handled by {@link MockIme}
1288      */
1289     @NonNull
callReportFullscreenMode(boolean enabled)1290     public ImeCommand callReportFullscreenMode(boolean enabled) {
1291         final Bundle params = new Bundle();
1292         params.putBoolean("enabled", enabled);
1293         return callCommandInternal("reportFullscreenMode", params);
1294     }
1295 
1296     /**
1297      * Lets {@link MockIme} to call {@link InputConnection#performPrivateCommand(String, Bundle)}
1298      * with the given parameters.
1299      *
1300      * <p>This triggers {@code getCurrentInputConnection().performPrivateCommand(action, data)}.</p>
1301      *
1302      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1303      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1304      * value returned from the API.</p>
1305      *
1306      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1307      *
1308      * @param action to be passed as the {@code action} parameter
1309      * @param data to be passed as the {@code data} parameter
1310      * @return {@link ImeCommand} object that can be passed to
1311      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1312      *         wait until this event is handled by {@link MockIme}
1313      */
1314     @NonNull
callPerformPrivateCommand(@ullable String action, Bundle data)1315     public ImeCommand callPerformPrivateCommand(@Nullable String action, Bundle data) {
1316         final Bundle params = new Bundle();
1317         params.putString("action", action);
1318         params.putBundle("data", data);
1319         return callCommandInternal("performPrivateCommand", params);
1320     }
1321 
1322     /**
1323      * Lets {@link MockIme} to call
1324      * {@link InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}
1325      * with the given parameters.
1326      *
1327      * <p>The result callback will be recorded as an {@code onPerformHandwritingGestureResult}
1328      * event.
1329      *
1330      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1331      *
1332      * @param gesture {@link SelectGesture} or {@link InsertGesture} or {@link DeleteGesture}.
1333      * @param useDelayedCancellation {@code true} to use delayed {@link CancellationSignal#cancel()}
1334      *  on a supported gesture like {@link android.view.inputmethod.InsertModeGesture}.
1335      * @return {@link ImeCommand} object that can be passed to
1336      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1337      *         wait until this event is handled by {@link MockIme}.
1338      */
1339     @NonNull
callPerformHandwritingGesture( @onNull HandwritingGesture gesture, boolean useDelayedCancellation)1340     public ImeCommand callPerformHandwritingGesture(
1341             @NonNull HandwritingGesture gesture, boolean useDelayedCancellation) {
1342         final Bundle params = new Bundle();
1343         params.putByteArray("gesture", gesture.toByteArray());
1344         params.putBoolean("useDelayedCancellation", useDelayedCancellation);
1345         return callCommandInternal("performHandwritingGesture", params);
1346     }
1347 
1348     /**
1349      * Lets {@link MockIme} to call {@link InputConnection#requestTextBoundsInfo}.
1350      *
1351      * <p>The result callback will be recorded as an {@code onRequestTextBoundsInfoResult} event.
1352      *
1353      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1354      *
1355      * @param gesture {@link SelectGesture} or {@link InsertGesture} or {@link DeleteGesture}.
1356      * @return {@link ImeCommand} object that can be passed to
1357      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1358      *         wait until this event is handled by {@link MockIme}.
1359      */
1360     @NonNull
callRequestTextBoundsInfo(RectF rectF)1361     public ImeCommand callRequestTextBoundsInfo(RectF rectF) {
1362         final Bundle params = new Bundle();
1363         params.putParcelable("rectF", rectF);
1364         return callCommandInternal("requestTextBoundsInfo", params);
1365     }
1366 
1367     /**
1368      * Lets {@link MockIme} to call
1369      * {@link InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture,
1370      *  CancellationSignal)} with the given parameters.
1371      *
1372      * <p>Use {@link ImeEvent#getReturnIntegerValue()} for {@link ImeEvent} returned from
1373      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1374      * value returned from the API.</p>
1375      *
1376      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1377      *
1378      * @param gesture one of {@link SelectGesture}, {@link SelectRangeGesture},
1379      * {@link DeleteGesture}, {@link DeleteRangeGesture}.
1380      * @param useDelayedCancellation {@code true} to use delayed {@link CancellationSignal#cancel()}
1381      *  on a gesture preview.
1382      * @return {@link ImeCommand} object that can be passed to
1383      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1384      *         wait until this event is handled by {@link MockIme}.
1385      */
1386     @NonNull
callPreviewHandwritingGesture( @onNull PreviewableHandwritingGesture gesture, boolean useDelayedCancellation)1387     public ImeCommand callPreviewHandwritingGesture(
1388             @NonNull PreviewableHandwritingGesture gesture, boolean useDelayedCancellation) {
1389         final Bundle params = new Bundle();
1390         params.putByteArray("gesture", gesture.toByteArray());
1391         params.putBoolean("useDelayedCancellation", useDelayedCancellation);
1392         return callCommandInternal("previewHandwritingGesture", params);
1393     }
1394 
1395     /**
1396      * Lets {@link MockIme} to call {@link InputConnection#requestCursorUpdates(int)} with the given
1397      * parameters.
1398      *
1399      * <p>This triggers {@code getCurrentInputConnection().requestCursorUpdates(cursorUpdateMode)}.
1400      * </p>
1401      *
1402      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1403      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1404      * value returned from the API.</p>
1405      *
1406      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1407      *
1408      * @param cursorUpdateMode to be passed as the {@code cursorUpdateMode} parameter
1409      * @return {@link ImeCommand} object that can be passed to
1410      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1411      *         wait until this event is handled by {@link MockIme}
1412      */
1413     @NonNull
callRequestCursorUpdates(int cursorUpdateMode)1414     public ImeCommand callRequestCursorUpdates(int cursorUpdateMode) {
1415         final Bundle params = new Bundle();
1416         params.putInt("cursorUpdateMode", cursorUpdateMode);
1417         return callCommandInternal("requestCursorUpdates", params);
1418     }
1419 
1420     /**
1421      * Lets {@link MockIme} to call {@link InputConnection#requestCursorUpdates(int, int)} with the
1422      * given parameters.
1423      *
1424      * <p>This triggers {@code getCurrentInputConnection().requestCursorUpdates(
1425      * cursorUpdateMode, cursorUpdateFilter)}.
1426      * </p>
1427      *
1428      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1429      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1430      * value returned from the API.</p>
1431      *
1432      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1433      *
1434      * @param cursorUpdateMode to be passed as the {@code cursorUpdateMode} parameter
1435      * @param cursorUpdateFilter to be passed as the {@code cursorUpdateFilter} parameter
1436      * @return {@link ImeCommand} object that can be passed to
1437      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1438      *         wait until this event is handled by {@link MockIme}
1439      */
1440     @NonNull
callRequestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter)1441     public ImeCommand callRequestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter) {
1442         final Bundle params = new Bundle();
1443         params.putInt("cursorUpdateMode", cursorUpdateMode);
1444         params.putInt("cursorUpdateFilter", cursorUpdateFilter);
1445         return callCommandInternal("requestCursorUpdates", params);
1446     }
1447 
1448     /**
1449      * Lets {@link MockIme} to call {@link InputConnection#getHandler()} with the given parameters.
1450      *
1451      * <p>This triggers {@code getCurrentInputConnection().getHandler()}.</p>
1452      *
1453      * <p>Use {@link ImeEvent#isNullReturnValue()} for {@link ImeEvent} returned from
1454      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1455      * value returned from the API was {@code null} or not.</p>
1456      *
1457      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1458      *
1459      * @return {@link ImeCommand} object that can be passed to
1460      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1461      *         wait until this event is handled by {@link MockIme}
1462      */
1463     @NonNull
callGetHandler()1464     public ImeCommand callGetHandler() {
1465         final Bundle params = new Bundle();
1466         return callCommandInternal("getHandler", params);
1467     }
1468 
1469     /**
1470      * Lets {@link MockIme} to call {@link InputConnection#closeConnection()} with the given
1471      * parameters.
1472      *
1473      * <p>This triggers {@code getCurrentInputConnection().closeConnection()}.</p>
1474      *
1475      * <p>Return value information is not available for this command.</p>
1476      *
1477      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1478      *
1479      * @return {@link ImeCommand} object that can be passed to
1480      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1481      *         wait until this event is handled by {@link MockIme}
1482      */
1483     @NonNull
callCloseConnection()1484     public ImeCommand callCloseConnection() {
1485         final Bundle params = new Bundle();
1486         return callCommandInternal("closeConnection", params);
1487     }
1488 
1489     /**
1490      * Lets {@link MockIme} to call
1491      * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} with the given
1492      * parameters.
1493      *
1494      * <p>This triggers
1495      * {@code getCurrentInputConnection().commitContent(inputContentInfo, flags, opts)}.</p>
1496      *
1497      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1498      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1499      * value returned from the API.</p>
1500      *
1501      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1502      *
1503      * @param inputContentInfo to be passed as the {@code inputContentInfo} parameter
1504      * @param flags to be passed as the {@code flags} parameter
1505      * @param opts to be passed as the {@code opts} parameter
1506      * @return {@link ImeCommand} object that can be passed to
1507      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1508      *         wait until this event is handled by {@link MockIme}
1509      */
1510     @NonNull
callCommitContent(@onNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts)1511     public ImeCommand callCommitContent(@NonNull InputContentInfo inputContentInfo, int flags,
1512             @Nullable Bundle opts) {
1513         final Bundle params = new Bundle();
1514         params.putParcelable("inputContentInfo", inputContentInfo);
1515         params.putInt("flags", flags);
1516         params.putBundle("opts", opts);
1517         return callCommandInternal("commitContent", params);
1518     }
1519 
1520     /**
1521      * Lets {@link MockIme} to call {@link InputConnection#setImeConsumesInput(boolean)} with the
1522      * given parameters.
1523      *
1524      * <p>This triggers {@code getCurrentInputConnection().setImeConsumesInput(boolean)}.</p>
1525      *
1526      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
1527      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
1528      * value returned from the API.</p>
1529      *
1530      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
1531      *
1532      * @param imeConsumesInput to be passed as the {@code imeConsumesInput} parameter
1533      * @return {@link ImeCommand} object that can be passed to
1534      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1535      *         wait until this event is handled by {@link MockIme}
1536      */
1537     @NonNull
callSetImeConsumesInput(boolean imeConsumesInput)1538     public ImeCommand callSetImeConsumesInput(boolean imeConsumesInput) {
1539         final Bundle params = new Bundle();
1540         params.putBoolean("imeConsumesInput", imeConsumesInput);
1541         return callCommandInternal("setImeConsumesInput", params);
1542     }
1543 
1544     /**
1545      * Lets {@link MockIme} to call {@link InputConnection#replaceText(int, int, CharSequence, int,
1546      * TextAttribute)} with the given parameters.
1547      *
1548      * <p>This triggers {@code getCurrentInputConnection().replaceText(int, int, CharSequence, int,
1549      * TextAttribute)}.
1550      *
1551      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from {@link
1552      * ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the value
1553      * returned from the API.
1554      *
1555      * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.
1556      *
1557      * @param start the character index where the replacement should start
1558      * @param end the character index where the replacement should end
1559      * @param newCursorPosition the new cursor position around the text. If > 0, this is relative to
1560      *     the end of the text - 1; if <= 0, this is relative to the start of the text. So a value
1561      *     of 1 will always advance you to the position after the full text being inserted. Note
1562      *     that this means you can't position the cursor within the text.
1563      * @param text the text to replace. This may include styles.
1564      * @param textAttribute The extra information about the text. This value may be null.
1565      * @return {@link ImeCommand} object that can be passed to {@link
1566      *     ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to wait until
1567      *     this event is handled by {@link MockIme}
1568      */
1569     @NonNull
callReplaceText( int start, int end, @NonNull CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute)1570     public ImeCommand callReplaceText(
1571             int start,
1572             int end,
1573             @NonNull CharSequence text,
1574             int newCursorPosition,
1575             @Nullable TextAttribute textAttribute) {
1576         final Bundle params = new Bundle();
1577         params.putInt("start", start);
1578         params.putInt("end", end);
1579         params.putCharSequence("text", text);
1580         params.putInt("newCursorPosition", newCursorPosition);
1581         params.putParcelable("textAttribute", textAttribute);
1582         return callCommandInternal("replaceText", params);
1583     }
1584 
1585     /**
1586      * Lets {@link MockIme} to call
1587      * {@link InputMethodManager#setExplicitlyEnabledInputMethodSubtypes(String, int[])} with the
1588      * given parameters.
1589      *
1590      * <p>This triggers {@code setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes)}.
1591      * </p>
1592      *
1593      * @param imeId the IME ID.
1594      * @param subtypeHashCodes An array of {@link InputMethodSubtype#hashCode()}. An empty array and
1595      *                   {@code null} can reset the enabled subtypes.
1596      * @return {@link ImeCommand} object that can be passed to
1597      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1598      *         wait until this event is handled by {@link MockIme}
1599      */
1600     @NonNull
callSetExplicitlyEnabledInputMethodSubtypes(String imeId, @Nullable int[] subtypeHashCodes)1601     public ImeCommand callSetExplicitlyEnabledInputMethodSubtypes(String imeId,
1602             @Nullable int[] subtypeHashCodes) {
1603         final Bundle params = new Bundle();
1604         params.putString("imeId", imeId);
1605         params.putIntArray("subtypeHashCodes", subtypeHashCodes);
1606         return callCommandInternal("setExplicitlyEnabledInputMethodSubtypes", params);
1607     }
1608 
1609     /**
1610      * Lets {@link MockIme} to call
1611      * {@link InputMethodManager#setAdditionalInputMethodSubtypes(String, InputMethodSubtype[])}
1612      * with the given parameters.
1613      *
1614      * @param imeId the IME ID
1615      * @param subtypes A non-null array of {@link InputMethodSubtype}
1616      * @return {@link ImeCommand} object that can be passed to
1617      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1618      *         wait until this event is handled by {@link MockIme}
1619      */
1620     @NonNull
callSetAdditionalInputMethodSubtypes(@onNull String imeId, @NonNull InputMethodSubtype[] subtypes)1621     public ImeCommand callSetAdditionalInputMethodSubtypes(@NonNull String imeId,
1622             @NonNull InputMethodSubtype[] subtypes) {
1623         final Bundle params = new Bundle();
1624         params.putString("imeId", imeId);
1625         params.putParcelableArray("subtypes", subtypes);
1626         return callCommandInternal("setAdditionalInputMethodSubtypes", params);
1627     }
1628 
1629     /**
1630      * Makes {@link MockIme} call {@link
1631      * android.inputmethodservice.InputMethodService#switchInputMethod(String)}
1632      * with the given parameters.
1633      *
1634      * @param id the IME ID.
1635      * @return {@link ImeCommand} object that can be passed to
1636      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1637      *         wait until this event is handled by {@link MockIme}
1638      */
1639     @NonNull
callSwitchInputMethod(String id)1640     public ImeCommand callSwitchInputMethod(String id) {
1641         final Bundle params = new Bundle();
1642         params.putString("id", id);
1643         return callCommandInternal("switchInputMethod", params);
1644     }
1645 
1646     /**
1647      * Lets {@link MockIme} to call {@link
1648      * android.inputmethodservice.InputMethodService#switchInputMethod(String, InputMethodSubtype)}
1649      * with the given parameters.
1650      *
1651      * <p>This triggers {@code switchInputMethod(id, subtype)}.</p>
1652      *
1653      * @param id the IME ID.
1654      * @param subtype {@link InputMethodSubtype} to be switched to. Ignored if {@code null}.
1655      * @return {@link ImeCommand} object that can be passed to
1656      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1657      *         wait until this event is handled by {@link MockIme}
1658      */
1659     @NonNull
callSwitchInputMethod(String id, @Nullable InputMethodSubtype subtype)1660     public ImeCommand callSwitchInputMethod(String id, @Nullable InputMethodSubtype subtype) {
1661         final Bundle params = new Bundle();
1662         params.putString("id", id);
1663         params.putParcelable("subtype", subtype);
1664         return callCommandInternal("switchInputMethod(String,InputMethodSubtype)", params);
1665     }
1666 
1667     /**
1668      * Lets {@link MockIme} to call
1669      * {@link android.inputmethodservice.InputMethodService#setBackDisposition(int)} with the given
1670      * parameters.
1671      *
1672      * <p>This triggers {@code setBackDisposition(backDisposition)}.</p>
1673      *
1674      * @param backDisposition to be passed as the {@code backDisposition} parameter
1675      * @return {@link ImeCommand} object that can be passed to
1676      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1677      *         wait until this event is handled by {@link MockIme}
1678      */
1679     @NonNull
callSetBackDisposition(int backDisposition)1680     public ImeCommand callSetBackDisposition(int backDisposition) {
1681         final Bundle params = new Bundle();
1682         params.putInt("backDisposition", backDisposition);
1683         return callCommandInternal("setBackDisposition", params);
1684     }
1685 
1686     /**
1687      * Lets {@link MockIme} to call
1688      * {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)} with the given
1689      * parameters.
1690      *
1691      * <p>This triggers {@code requestHideSelf(flags)}.</p>
1692      *
1693      * @param flags to be passed as the {@code flags} parameter
1694      * @return {@link ImeCommand} object that can be passed to
1695      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1696      *         wait until this event is handled by {@link MockIme}
1697      */
1698     @NonNull
callRequestHideSelf(int flags)1699     public ImeCommand callRequestHideSelf(int flags) {
1700         final Bundle params = new Bundle();
1701         params.putInt("flags", flags);
1702         return callCommandInternal("requestHideSelf", params);
1703     }
1704 
1705     /**
1706      * Lets {@link MockIme} to call
1707      * {@link android.inputmethodservice.InputMethodService#requestShowSelf(int)} with the given
1708      * parameters.
1709      *
1710      * <p>This triggers {@code requestShowSelf(flags)}.</p>
1711      *
1712      * @param flags to be passed as the {@code flags} parameter
1713      * @return {@link ImeCommand} object that can be passed to
1714      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1715      *         wait until this event is handled by {@link MockIme}
1716      */
1717     @NonNull
callRequestShowSelf(int flags)1718     public ImeCommand callRequestShowSelf(int flags) {
1719         final Bundle params = new Bundle();
1720         params.putInt("flags", flags);
1721         return callCommandInternal("requestShowSelf", params);
1722     }
1723 
1724     /**
1725      * Requests hiding the current soft input window, with the request origin on the server side.
1726      *
1727      * @see InputMethodManager#hideSoftInputFromServerForTest()
1728      */
hideSoftInputFromServerForTest()1729     public void hideSoftInputFromServerForTest() {
1730         final var imm = mContext.getSystemService(InputMethodManager.class);
1731         runWithShellPermissionIdentity(imm::hideSoftInputFromServerForTest);
1732     }
1733 
1734     /**
1735      * Lets {@link MockIme} call
1736      * {@link android.inputmethodservice.InputMethodService#sendDownUpKeyEvents(int)} with the given
1737      * {@code keyEventCode}.
1738      *
1739      * @param keyEventCode to be passed as the {@code keyEventCode} parameter.
1740      * @return {@link ImeCommand} object that can be passed to
1741      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1742      *         wait until this event is handled by {@link MockIme}
1743      */
1744     @NonNull
callSendDownUpKeyEvents(int keyEventCode)1745     public ImeCommand callSendDownUpKeyEvents(int keyEventCode) {
1746         final Bundle params = new Bundle();
1747         params.putInt("keyEventCode", keyEventCode);
1748         return callCommandInternal("sendDownUpKeyEvents", params);
1749     }
1750 
1751     /**
1752      * Lets {@link MockIme} call
1753      * {@link android.content.pm.PackageManager#getApplicationInfo(String, int)} with the given
1754      * {@code packageName} and {@code flags}.
1755      *
1756      * @param packageName the package name to be passed to
1757      *                    {@link android.content.pm.PackageManager#getApplicationInfo(String, int)}.
1758      * @param flags the flags to be passed to
1759      *                    {@link android.content.pm.PackageManager#getApplicationInfo(String, int)}.
1760      * @return {@link ImeCommand} object that can be passed to
1761      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1762      *         wait until this event is handled by {@link MockIme}.
1763      */
1764     @NonNull
callGetApplicationInfo(@onNull String packageName, int flags)1765     public ImeCommand callGetApplicationInfo(@NonNull String packageName, int flags) {
1766         final Bundle params = new Bundle();
1767         params.putString("packageName", packageName);
1768         params.putInt("flags", flags);
1769         return callCommandInternal("getApplicationInfo", params);
1770     }
1771 
1772     @NonNull
callSetEnableOnBackInvokedCallback(Boolean isEnabled)1773     public ImeCommand callSetEnableOnBackInvokedCallback(Boolean isEnabled) {
1774         final Bundle params = new Bundle();
1775         params.putBoolean("isEnabled", isEnabled);
1776         return callCommandInternal("setEnableOnBackInvokedCallback", params);
1777     }
1778 
1779     @NonNull
callGetDisplayId()1780     public ImeCommand callGetDisplayId() {
1781         final Bundle params = new Bundle();
1782         return callCommandInternal("getDisplayId", params);
1783     }
1784 
1785     /**
1786      * Calls and returns value of
1787      * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode}.
1788      */
1789     @NonNull
callGetOnEvaluateFullscreenMode()1790     public ImeCommand callGetOnEvaluateFullscreenMode() {
1791         return callCommandInternal("getOnEvaluateFullscreenMode", new Bundle());
1792     }
1793 
1794     /**
1795      * Verifies {@code InputMethodService.getLayoutInflater().getContext()} is equal to
1796      * {@code InputMethodService.this}.
1797      *
1798      * @return {@link ImeCommand} object that can be passed to
1799      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1800      *         wait until this event is handled by {@link MockIme}
1801      */
1802     @NonNull
verifyLayoutInflaterContext()1803     public ImeCommand verifyLayoutInflaterContext() {
1804         final Bundle params = new Bundle();
1805         return callCommandInternal("verifyLayoutInflaterContext", params);
1806     }
1807 
1808     @NonNull
callSetHeight(int height)1809     public ImeCommand callSetHeight(int height) {
1810         final Bundle params = new Bundle();
1811         params.putInt("height", height);
1812         return callCommandInternal("setHeight", params);
1813     }
1814 
1815     @NonNull
callSetInlineSuggestionsExtras(@onNull Bundle bundle)1816     public void callSetInlineSuggestionsExtras(@NonNull Bundle bundle) {
1817         MultiUserUtils.callContentProvider(mContext, mUiAutomation,
1818                 mMockImeSettingsProviderAuthority,
1819                 SettingsProvider.SET_INLINE_SUGGESTION_EXTRAS_COMMAND, null /* args */, bundle,
1820                 mTargetUser);
1821     }
1822 
1823     /**
1824      * Lets {@link MockIme} call
1825      * {@link android.inputmethodservice.InputMethodService#setExtractView(View)} with a custom
1826      * extract view hierarchy.
1827      *
1828      * @param label The label text to show in the extract view hierarchy.
1829      * @return {@link ImeCommand} object that can be passed to
1830      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1831      *         wait until this event is handled by {@link MockIme}.
1832      */
1833     @NonNull
callSetExtractView(String label)1834     public ImeCommand callSetExtractView(String label) {
1835         Bundle params = new Bundle();
1836         params.putString("label", label);
1837         return callCommandInternal("setExtractView", params);
1838     }
1839 
1840     @NonNull
callVerifyExtractViewNotNull()1841     public ImeCommand callVerifyExtractViewNotNull() {
1842         return callCommandInternal("verifyExtractViewNotNull", new Bundle());
1843     }
1844 
1845     @NonNull
callVerifyGetDisplay()1846     public ImeCommand callVerifyGetDisplay() {
1847         return callCommandInternal("verifyGetDisplay", new Bundle());
1848     }
1849 
1850     @NonNull
callVerifyIsUiContext()1851     public ImeCommand callVerifyIsUiContext() {
1852         return callCommandInternal("verifyIsUiContext", new Bundle());
1853     }
1854 
1855     @NonNull
callVerifyGetWindowManager()1856     public ImeCommand callVerifyGetWindowManager() {
1857         return callCommandInternal("verifyGetWindowManager", new Bundle());
1858     }
1859 
1860     @NonNull
callVerifyGetViewConfiguration()1861     public ImeCommand callVerifyGetViewConfiguration() {
1862         return callCommandInternal("verifyGetViewConfiguration", new Bundle());
1863     }
1864 
1865     @NonNull
callVerifyGetGestureDetector()1866     public ImeCommand callVerifyGetGestureDetector() {
1867         return callCommandInternal("verifyGetGestureDetector", new Bundle());
1868     }
1869 
1870     @NonNull
callVerifyGetWindowManagerOnDisplayContext()1871     public ImeCommand callVerifyGetWindowManagerOnDisplayContext() {
1872         return callCommandInternal("verifyGetWindowManagerOnDisplayContext", new Bundle());
1873     }
1874 
1875     @NonNull
callVerifyGetViewConfigurationOnDisplayContext()1876     public ImeCommand callVerifyGetViewConfigurationOnDisplayContext() {
1877         return callCommandInternal("verifyGetViewConfigurationOnDisplayContext", new Bundle());
1878     }
1879 
1880     @NonNull
callVerifyGetGestureDetectorOnDisplayContext()1881     public ImeCommand callVerifyGetGestureDetectorOnDisplayContext() {
1882         return callCommandInternal("verifyGetGestureDetectorOnDisplayContext", new Bundle());
1883     }
1884 
1885     @NonNull
callGetStylusHandwritingWindowVisibility()1886     public ImeCommand callGetStylusHandwritingWindowVisibility() {
1887         return callCommandInternal("getStylusHandwritingWindowVisibility", new Bundle());
1888     }
1889 
1890     @NonNull
callGetWindowLayoutInfo()1891     public ImeCommand callGetWindowLayoutInfo() {
1892         return callCommandInternal("getWindowLayoutInfo", new Bundle());
1893     }
1894 
1895     @NonNull
callHasStylusHandwritingWindow()1896     public ImeCommand callHasStylusHandwritingWindow() {
1897         return callCommandInternal("hasStylusHandwritingWindow", new Bundle());
1898     }
1899 
1900     @NonNull
callSetStylusHandwritingInkView()1901     public ImeCommand callSetStylusHandwritingInkView() {
1902         return callCommandInternal("setStylusHandwritingInkView", new Bundle());
1903     }
1904 
1905     @NonNull
callSetStylusHandwritingTimeout(long timeoutMs)1906     public ImeCommand callSetStylusHandwritingTimeout(long timeoutMs) {
1907         Bundle params = new Bundle();
1908         params.putLong("timeoutMs", timeoutMs);
1909         return callCommandInternal("setStylusHandwritingTimeout", params);
1910     }
1911 
1912     @NonNull
callGetStylusHandwritingTimeout()1913     public ImeCommand callGetStylusHandwritingTimeout() {
1914         return callCommandInternal("getStylusHandwritingTimeout", new Bundle());
1915     }
1916 
1917     @NonNull
callGetStylusHandwritingEvents()1918     public ImeCommand callGetStylusHandwritingEvents() {
1919         return callCommandInternal("getStylusHandwritingEvents", new Bundle());
1920     }
1921 
1922     @NonNull
callFinishStylusHandwriting()1923     public ImeCommand callFinishStylusHandwriting() {
1924         return callCommandInternal("finishStylusHandwriting", new Bundle());
1925     }
1926 
1927     /**
1928      * Lets {@link MockIme} call
1929      * {@link android.inputmethodservice.InputMethodService#finishConnectionlessStylusHandwriting}
1930      * with the given {@code text}.
1931      */
1932     @NonNull
callFinishConnectionlessStylusHandwriting(CharSequence text)1933     public ImeCommand callFinishConnectionlessStylusHandwriting(CharSequence text) {
1934         Bundle params = new Bundle();
1935         params.putCharSequence("text", text);
1936         return callCommandInternal("finishConnectionlessStylusHandwriting", params);
1937     }
1938 
1939     @NonNull
callGetCurrentWindowMetricsBounds()1940     public ImeCommand callGetCurrentWindowMetricsBounds() {
1941         return callCommandInternal("getCurrentWindowMetricsBounds", new Bundle());
1942     }
1943 
1944     @NonNull
callSetImeCaptionBarVisible(boolean visible)1945     public ImeCommand callSetImeCaptionBarVisible(boolean visible) {
1946         final Bundle params = new Bundle();
1947         params.putBoolean("visible", visible);
1948         return callCommandInternal("setImeCaptionBarVisible", params);
1949     }
1950 
1951     @NonNull
callGetImeCaptionBarHeight()1952     public ImeCommand callGetImeCaptionBarHeight() {
1953         return callCommandInternal("getImeCaptionBarHeight", new Bundle());
1954     }
1955 }
1956