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