• 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import android.app.UiAutomation;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.PackageManager;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.os.ParcelFileDescriptor;
33 import android.os.SystemClock;
34 import android.provider.Settings;
35 import android.text.TextUtils;
36 import android.view.KeyEvent;
37 import android.view.inputmethod.CompletionInfo;
38 import android.view.inputmethod.CorrectionInfo;
39 import android.view.inputmethod.ExtractedTextRequest;
40 import android.view.inputmethod.InputConnection;
41 import android.view.inputmethod.InputContentInfo;
42 import android.view.inputmethod.InputMethodManager;
43 
44 import androidx.annotation.GuardedBy;
45 import androidx.annotation.NonNull;
46 import androidx.annotation.Nullable;
47 
48 import com.android.compatibility.common.util.PollingCheck;
49 import com.android.compatibility.common.util.SystemUtil;
50 
51 import org.junit.AssumptionViolatedException;
52 
53 import java.io.IOException;
54 import java.util.concurrent.TimeUnit;
55 
56 /**
57  * Represents an active Mock IME session, which provides basic primitives to write end-to-end tests
58  * for IME APIs.
59  *
60  * <p>To use {@link MockIme} via {@link MockImeSession}, you need to </p>
61  * <p>Public methods are not thread-safe.</p>
62  */
63 public class MockImeSession implements AutoCloseable {
64     private final String mImeEventActionName =
65             "com.android.cts.mockime.action.IME_EVENT." + SystemClock.elapsedRealtimeNanos();
66 
67     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10);
68 
69     @NonNull
70     private final Context mContext;
71     @NonNull
72     private final UiAutomation mUiAutomation;
73 
74     private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
75 
76     private static final class EventStore {
77         private static final int INITIAL_ARRAY_SIZE = 32;
78 
79         @NonNull
80         public final ImeEvent[] mArray;
81         public int mLength;
82 
EventStore()83         EventStore() {
84             mArray = new ImeEvent[INITIAL_ARRAY_SIZE];
85             mLength = 0;
86         }
87 
EventStore(EventStore src, int newLength)88         EventStore(EventStore src, int newLength) {
89             mArray = new ImeEvent[newLength];
90             mLength = src.mLength;
91             System.arraycopy(src.mArray, 0, mArray, 0, src.mLength);
92         }
93 
add(ImeEvent event)94         public EventStore add(ImeEvent event) {
95             if (mLength + 1 <= mArray.length) {
96                 mArray[mLength] = event;
97                 ++mLength;
98                 return this;
99             } else {
100                 return new EventStore(this, mLength * 2).add(event);
101             }
102         }
103 
takeSnapshot()104         public ImeEventStream.ImeEventArray takeSnapshot() {
105             return new ImeEventStream.ImeEventArray(mArray, mLength);
106         }
107     }
108 
109     private static final class MockImeEventReceiver extends BroadcastReceiver {
110         private final Object mLock = new Object();
111 
112         @GuardedBy("mLock")
113         @NonNull
114         private EventStore mCurrentEventStore = new EventStore();
115 
116         @NonNull
117         private final String mActionName;
118 
MockImeEventReceiver(@onNull String actionName)119         MockImeEventReceiver(@NonNull String actionName) {
120             mActionName = actionName;
121         }
122 
123         @Override
onReceive(Context context, Intent intent)124         public void onReceive(Context context, Intent intent) {
125             if (TextUtils.equals(mActionName, intent.getAction())) {
126                 synchronized (mLock) {
127                     mCurrentEventStore =
128                             mCurrentEventStore.add(ImeEvent.fromBundle(intent.getExtras()));
129                 }
130             }
131         }
132 
takeEventSnapshot()133         public ImeEventStream.ImeEventArray takeEventSnapshot() {
134             synchronized (mLock) {
135                 return mCurrentEventStore.takeSnapshot();
136             }
137         }
138     }
139     private final MockImeEventReceiver mEventReceiver =
140             new MockImeEventReceiver(mImeEventActionName);
141 
142     private final ImeEventStream mEventStream =
143             new ImeEventStream(mEventReceiver::takeEventSnapshot);
144 
executeShellCommand( @onNull UiAutomation uiAutomation, @NonNull String command)145     private static String executeShellCommand(
146             @NonNull UiAutomation uiAutomation, @NonNull String command) throws IOException {
147         try (ParcelFileDescriptor.AutoCloseInputStream in =
148                      new ParcelFileDescriptor.AutoCloseInputStream(
149                              uiAutomation.executeShellCommand(command))) {
150             final StringBuilder sb = new StringBuilder();
151             final byte[] buffer = new byte[4096];
152             while (true) {
153                 final int numRead = in.read(buffer);
154                 if (numRead <= 0) {
155                     break;
156                 }
157                 sb.append(new String(buffer, 0, numRead));
158             }
159             return sb.toString();
160         }
161     }
162 
163     @Nullable
getCurrentInputMethodId()164     private String getCurrentInputMethodId() {
165         // TODO: Replace this with IMM#getCurrentInputMethodIdForTesting()
166         return Settings.Secure.getString(mContext.getContentResolver(),
167                 Settings.Secure.DEFAULT_INPUT_METHOD);
168     }
169 
170     @Nullable
writeMockImeSettings(@onNull Context context, @NonNull String imeEventActionName, @Nullable ImeSettings.Builder imeSettings)171     private static void writeMockImeSettings(@NonNull Context context,
172             @NonNull String imeEventActionName,
173             @Nullable ImeSettings.Builder imeSettings) throws Exception {
174         final Bundle bundle = ImeSettings.serializeToBundle(imeEventActionName, imeSettings);
175         context.getContentResolver().call(SettingsProvider.AUTHORITY, "write", null, bundle);
176     }
177 
getMockImeComponentName()178     private ComponentName getMockImeComponentName() {
179         return MockIme.getComponentName();
180     }
181 
getMockImeId()182     private String getMockImeId() {
183         return MockIme.getImeId();
184     }
185 
MockImeSession(@onNull Context context, @NonNull UiAutomation uiAutomation)186     private MockImeSession(@NonNull Context context, @NonNull UiAutomation uiAutomation) {
187         mContext = context;
188         mUiAutomation = uiAutomation;
189     }
190 
initialize(@ullable ImeSettings.Builder imeSettings)191     private void initialize(@Nullable ImeSettings.Builder imeSettings) throws Exception {
192         // Make sure that MockIME is not selected.
193         if (mContext.getSystemService(InputMethodManager.class)
194                 .getInputMethodList()
195                 .stream()
196                 .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
197             executeShellCommand(mUiAutomation, "ime reset");
198         }
199         if (mContext.getSystemService(InputMethodManager.class)
200                 .getEnabledInputMethodList()
201                 .stream()
202                 .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
203             throw new IllegalStateException();
204         }
205 
206         writeMockImeSettings(mContext, mImeEventActionName, imeSettings);
207 
208         mHandlerThread.start();
209         mContext.registerReceiver(mEventReceiver,
210                 new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
211                 new Handler(mHandlerThread.getLooper()));
212 
213         executeShellCommand(mUiAutomation, "ime enable " + getMockImeId());
214         executeShellCommand(mUiAutomation, "ime set " + getMockImeId());
215 
216         PollingCheck.check("Make sure that MockIME becomes available", TIMEOUT,
217                 () -> getMockImeId().equals(getCurrentInputMethodId()));
218     }
219 
220     /** @see #create(Context, UiAutomation, ImeSettings.Builder) */
221     @NonNull
create(@onNull Context context)222     public static MockImeSession create(@NonNull Context context) throws Exception {
223         return create(context, getInstrumentation().getUiAutomation(), new ImeSettings.Builder());
224     }
225 
226     /**
227      * Creates a new Mock IME session. During this session, you can receive various events from
228      * {@link MockIme}.
229      *
230      * @param context {@link Context} to be used to receive inter-process events from the
231      *                {@link MockIme} (e.g. via {@link BroadcastReceiver}
232      * @param uiAutomation {@link UiAutomation} object to change the device state that are typically
233      *                     guarded by permissions.
234      * @param imeSettings Key-value pairs to be passed to the {@link MockIme}.
235      * @return A session object, with which you can retrieve event logs from the {@link MockIme} and
236      *         can clean up the session.
237      */
238     @NonNull
create( @onNull Context context, @NonNull UiAutomation uiAutomation, @Nullable ImeSettings.Builder imeSettings)239     public static MockImeSession create(
240             @NonNull Context context,
241             @NonNull UiAutomation uiAutomation,
242             @Nullable ImeSettings.Builder imeSettings) throws Exception {
243         final String unavailabilityReason = getUnavailabilityReason(context);
244         if (unavailabilityReason != null) {
245             throw new AssumptionViolatedException(unavailabilityReason);
246         }
247 
248         final MockImeSession client = new MockImeSession(context, uiAutomation);
249         client.initialize(imeSettings);
250         return client;
251     }
252 
253     /**
254      * Checks if the {@link MockIme} can be used in this device.
255      *
256      * @return {@code null} if it can be used, or message describing why if it cannot.
257      */
258     @Nullable
getUnavailabilityReason(@onNull Context context)259     public static String getUnavailabilityReason(@NonNull Context context) {
260         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
261             return "Device must support installable IMEs that implement InputMethodService API";
262         }
263         return null;
264     }
265 
266     /**
267      * @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
268      *         session is created.
269      */
openEventStream()270     public ImeEventStream openEventStream() {
271         return mEventStream.copy();
272     }
273 
274     /**
275      * Closes the active session and de-selects {@link MockIme}. Currently which IME will be
276      * selected next is up to the system.
277      */
close()278     public void close() throws Exception {
279         executeShellCommand(mUiAutomation, "ime reset");
280 
281         PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
282                 mContext.getSystemService(InputMethodManager.class)
283                         .getEnabledInputMethodList()
284                         .stream()
285                         .noneMatch(info -> getMockImeComponentName().equals(info.getComponent())));
286 
287         mContext.unregisterReceiver(mEventReceiver);
288         mHandlerThread.quitSafely();
289         mContext.getContentResolver().call(SettingsProvider.AUTHORITY, "delete", null, null);
290     }
291 
292     /**
293      * Common logic to send a special command to {@link MockIme}.
294      *
295      * @param commandName command to be passed to {@link MockIme}
296      * @param params {@link Bundle} to be passed to {@link MockIme} as a parameter set of
297      *               {@code commandName}
298      * @return {@link ImeCommand} that is sent to {@link MockIme}.  It can be passed to
299      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
300      *         wait until this event is handled by {@link MockIme}.
301      */
302     @NonNull
callCommandInternal(@onNull String commandName, @NonNull Bundle params)303     private ImeCommand callCommandInternal(@NonNull String commandName, @NonNull Bundle params) {
304         final ImeCommand command = new ImeCommand(
305                 commandName, SystemClock.elapsedRealtimeNanos(), true, params);
306         final Intent intent = new Intent();
307         intent.setPackage(MockIme.getComponentName().getPackageName());
308         intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
309         intent.putExtras(command.toBundle());
310         mContext.sendBroadcast(intent);
311         return command;
312     }
313 
314     /**
315      * Lets {@link MockIme} to call {@link InputConnection#getTextBeforeCursor(int, int)} with the
316      * given parameters.
317      *
318      * <p>This triggers {@code getCurrentInputConnection().getTextBeforeCursor(n, flag)}.</p>
319      *
320      * <p>Use {@link ImeEvent#getReturnCharSequenceValue()} for {@link ImeEvent} returned from
321      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
322      * value returned from the API.</p>
323      *
324      * @param n to be passed as the {@code n} parameter.
325      * @param flag to be passed as the {@code flag} parameter.
326      * @return {@link ImeCommand} object that can be passed to
327      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
328      *         wait until this event is handled by {@link MockIme}.
329      */
330     @NonNull
callGetTextBeforeCursor(int n, int flag)331     public ImeCommand callGetTextBeforeCursor(int n, int flag) {
332         final Bundle params = new Bundle();
333         params.putInt("n", n);
334         params.putInt("flag", flag);
335         return callCommandInternal("getTextBeforeCursor", params);
336     }
337 
338     /**
339      * Lets {@link MockIme} to call {@link InputConnection#getTextAfterCursor(int, int)} with the
340      * given parameters.
341      *
342      * <p>This triggers {@code getCurrentInputConnection().getTextAfterCursor(n, flag)}.</p>
343      *
344      * <p>Use {@link ImeEvent#getReturnCharSequenceValue()} for {@link ImeEvent} returned from
345      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
346      * value returned from the API.</p>
347      *
348      * @param n to be passed as the {@code n} parameter.
349      * @param flag to be passed as the {@code flag} parameter.
350      * @return {@link ImeCommand} object that can be passed to
351      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
352      *         wait until this event is handled by {@link MockIme}.
353      */
354     @NonNull
callGetTextAfterCursor(int n, int flag)355     public ImeCommand callGetTextAfterCursor(int n, int flag) {
356         final Bundle params = new Bundle();
357         params.putInt("n", n);
358         params.putInt("flag", flag);
359         return callCommandInternal("getTextAfterCursor", params);
360     }
361 
362     /**
363      * Lets {@link MockIme} to call {@link InputConnection#getSelectedText(int)} with the
364      * given parameters.
365      *
366      * <p>This triggers {@code getCurrentInputConnection().getSelectedText(flag)}.</p>
367      *
368      * <p>Use {@link ImeEvent#getReturnCharSequenceValue()} for {@link ImeEvent} returned from
369      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
370      * value returned from the API.</p>
371      *
372      * @param flag to be passed as the {@code flag} parameter.
373      * @return {@link ImeCommand} object that can be passed to
374      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
375      *         wait until this event is handled by {@link MockIme}.
376      */
377     @NonNull
callGetSelectedText(int flag)378     public ImeCommand callGetSelectedText(int flag) {
379         final Bundle params = new Bundle();
380         params.putInt("flag", flag);
381         return callCommandInternal("getSelectedText", params);
382     }
383 
384     /**
385      * Lets {@link MockIme} to call {@link InputConnection#getCursorCapsMode(int)} with the given
386      * parameters.
387      *
388      * <p>This triggers {@code getCurrentInputConnection().getCursorCapsMode(reqModes)}.</p>
389      *
390      * <p>Use {@link ImeEvent#getReturnIntegerValue()} for {@link ImeEvent} returned from
391      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
392      * value returned from the API.</p>
393      *
394      * @param reqModes to be passed as the {@code reqModes} parameter.
395      * @return {@link ImeCommand} object that can be passed to
396      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
397      *         wait until this event is handled by {@link MockIme}.
398      */
399     @NonNull
callGetCursorCapsMode(int reqModes)400     public ImeCommand callGetCursorCapsMode(int reqModes) {
401         final Bundle params = new Bundle();
402         params.putInt("reqModes", reqModes);
403         return callCommandInternal("getCursorCapsMode", params);
404     }
405 
406     /**
407      * Lets {@link MockIme} to call
408      * {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} with the given
409      * parameters.
410      *
411      * <p>This triggers {@code getCurrentInputConnection().getExtractedText(request, flags)}.</p>
412      *
413      * <p>Use {@link ImeEvent#getReturnParcelableValue()} for {@link ImeEvent} returned from
414      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
415      * value returned from the API.</p>
416      *
417      * @param request to be passed as the {@code request} parameter
418      * @param flags to be passed as the {@code flags} parameter
419      * @return {@link ImeCommand} object that can be passed to
420      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
421      *         wait until this event is handled by {@link MockIme}.
422      */
423     @NonNull
callGetExtractedText(@ullable ExtractedTextRequest request, int flags)424     public ImeCommand callGetExtractedText(@Nullable ExtractedTextRequest request, int flags) {
425         final Bundle params = new Bundle();
426         params.putParcelable("request", request);
427         params.putInt("flags", flags);
428         return callCommandInternal("getExtractedText", params);
429     }
430 
431     /**
432      * Lets {@link MockIme} to call {@link InputConnection#deleteSurroundingText(int, int)} with the
433      * given parameters.
434      *
435      * <p>This triggers
436      * {@code getCurrentInputConnection().deleteSurroundingText(beforeLength, afterLength)}.</p>
437      *
438      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
439      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
440      * value returned from the API.</p>
441      *
442      * @param beforeLength to be passed as the {@code beforeLength} parameter
443      * @param afterLength to be passed as the {@code afterLength} parameter
444      * @return {@link ImeCommand} object that can be passed to
445      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
446      *         wait until this event is handled by {@link MockIme}.
447      */
448     @NonNull
callDeleteSurroundingText(int beforeLength, int afterLength)449     public ImeCommand callDeleteSurroundingText(int beforeLength, int afterLength) {
450         final Bundle params = new Bundle();
451         params.putInt("beforeLength", beforeLength);
452         params.putInt("afterLength", afterLength);
453         return callCommandInternal("deleteSurroundingText", params);
454     }
455 
456     /**
457      * Lets {@link MockIme} to call
458      * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)} with the given
459      * parameters.
460      *
461      * <p>This triggers {@code getCurrentInputConnection().deleteSurroundingTextInCodePoints(
462      * beforeLength, afterLength)}.</p>
463      *
464      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
465      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
466      * value returned from the API.</p>
467      *
468      * @param beforeLength to be passed as the {@code beforeLength} parameter
469      * @param afterLength to be passed as the {@code afterLength} parameter
470      * @return {@link ImeCommand} object that can be passed to
471      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
472      *         wait until this event is handled by {@link MockIme}.
473      */
474     @NonNull
callDeleteSurroundingTextInCodePoints(int beforeLength, int afterLength)475     public ImeCommand callDeleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
476         final Bundle params = new Bundle();
477         params.putInt("beforeLength", beforeLength);
478         params.putInt("afterLength", afterLength);
479         return callCommandInternal("deleteSurroundingTextInCodePoints", params);
480     }
481 
482     /**
483      * Lets {@link MockIme} to call {@link InputConnection#setComposingText(CharSequence, int)} with
484      * the given parameters.
485      *
486      * <p>This triggers
487      * {@code getCurrentInputConnection().setComposingText(text, newCursorPosition)}.</p>
488      *
489      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
490      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
491      * value returned from the API.</p>
492      *
493      * @param text to be passed as the {@code text} parameter
494      * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
495      * @return {@link ImeCommand} object that can be passed to
496      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
497      *         wait until this event is handled by {@link MockIme}.
498      */
499     @NonNull
callSetComposingText(@ullable CharSequence text, int newCursorPosition)500     public ImeCommand callSetComposingText(@Nullable CharSequence text, int newCursorPosition) {
501         final Bundle params = new Bundle();
502         params.putCharSequence("text", text);
503         params.putInt("newCursorPosition", newCursorPosition);
504         return callCommandInternal("setComposingText", params);
505     }
506 
507     /**
508      * Lets {@link MockIme} to call {@link InputConnection#setComposingRegion(int, int)} with the
509      * given parameters.
510      *
511      * <p>This triggers {@code getCurrentInputConnection().setComposingRegion(start, end)}.</p>
512      *
513      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
514      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
515      * value returned from the API.</p>
516      *
517      * @param start to be passed as the {@code start} parameter
518      * @param end to be passed as the {@code end} parameter
519      * @return {@link ImeCommand} object that can be passed to
520      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
521      *         wait until this event is handled by {@link MockIme}.
522      */
523     @NonNull
callSetComposingRegion(int start, int end)524     public ImeCommand callSetComposingRegion(int start, int end) {
525         final Bundle params = new Bundle();
526         params.putInt("start", start);
527         params.putInt("end", end);
528         return callCommandInternal("setComposingRegion", params);
529     }
530 
531     /**
532      * Lets {@link MockIme} to call {@link InputConnection#finishComposingText()} with the given
533      * parameters.
534      *
535      * <p>This triggers {@code getCurrentInputConnection().finishComposingText()}.</p>
536      *
537      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
538      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
539      * value returned from the API.</p>
540      *
541      * @return {@link ImeCommand} object that can be passed to
542      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
543      *         wait until this event is handled by {@link MockIme}.
544      */
545     @NonNull
callFinishComposingText()546     public ImeCommand callFinishComposingText() {
547         final Bundle params = new Bundle();
548         return callCommandInternal("finishComposingText", params);
549     }
550 
551     /**
552      * Lets {@link MockIme} to call {@link InputConnection#commitText(CharSequence, int)} with the
553      * given parameters.
554      *
555      * <p>This triggers {@code getCurrentInputConnection().commitText(text, newCursorPosition)}.</p>
556      *
557      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
558      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
559      * value returned from the API.</p>
560      *
561      * @param text to be passed as the {@code text} parameter
562      * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
563      * @return {@link ImeCommand} object that can be passed to
564      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
565      *         wait until this event is handled by {@link MockIme}
566      */
567     @NonNull
callCommitText(@ullable CharSequence text, int newCursorPosition)568     public ImeCommand callCommitText(@Nullable CharSequence text, int newCursorPosition) {
569         final Bundle params = new Bundle();
570         params.putCharSequence("text", text);
571         params.putInt("newCursorPosition", newCursorPosition);
572         return callCommandInternal("commitText", params);
573     }
574 
575     /**
576      * Lets {@link MockIme} to call {@link InputConnection#commitCompletion(CompletionInfo)} with
577      * the given parameters.
578      *
579      * <p>This triggers {@code getCurrentInputConnection().commitCompletion(text)}.</p>
580      *
581      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
582      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
583      * value returned from the API.</p>
584      *
585      * @param text to be passed as the {@code text} parameter
586      * @return {@link ImeCommand} object that can be passed to
587      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
588      *         wait until this event is handled by {@link MockIme}
589      */
590     @NonNull
callCommitCompletion(@ullable CompletionInfo text)591     public ImeCommand callCommitCompletion(@Nullable CompletionInfo text) {
592         final Bundle params = new Bundle();
593         params.putParcelable("text", text);
594         return callCommandInternal("commitCompletion", params);
595     }
596 
597     /**
598      * Lets {@link MockIme} to call {@link InputConnection#commitCorrection(CorrectionInfo)} with
599      * the given parameters.
600      *
601      * <p>This triggers {@code getCurrentInputConnection().commitCorrection(correctionInfo)}.</p>
602      *
603      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
604      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
605      * value returned from the API.</p>
606      *
607      * @param correctionInfo to be passed as the {@code correctionInfo} parameter
608      * @return {@link ImeCommand} object that can be passed to
609      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
610      *         wait until this event is handled by {@link MockIme}
611      */
612     @NonNull
callCommitCorrection(@ullable CorrectionInfo correctionInfo)613     public ImeCommand callCommitCorrection(@Nullable CorrectionInfo correctionInfo) {
614         final Bundle params = new Bundle();
615         params.putParcelable("correctionInfo", correctionInfo);
616         return callCommandInternal("commitCorrection", params);
617     }
618 
619     /**
620      * Lets {@link MockIme} to call {@link InputConnection#setSelection(int, int)} with the given
621      * parameters.
622      *
623      * <p>This triggers {@code getCurrentInputConnection().setSelection(start, end)}.</p>
624      *
625      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
626      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
627      * value returned from the API.</p>
628      *
629      * @param start to be passed as the {@code start} parameter
630      * @param end to be passed as the {@code end} parameter
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      */
635     @NonNull
callSetSelection(int start, int end)636     public ImeCommand callSetSelection(int start, int end) {
637         final Bundle params = new Bundle();
638         params.putInt("start", start);
639         params.putInt("end", end);
640         return callCommandInternal("setSelection", params);
641     }
642 
643     /**
644      * Lets {@link MockIme} to call {@link InputConnection#performEditorAction(int)} with the given
645      * parameters.
646      *
647      * <p>This triggers {@code getCurrentInputConnection().performEditorAction(editorAction)}.</p>
648      *
649      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
650      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
651      * value returned from the API.</p>
652      *
653      * @param editorAction to be passed as the {@code editorAction} parameter
654      * @return {@link ImeCommand} object that can be passed to
655      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
656      *         wait until this event is handled by {@link MockIme}
657      */
658     @NonNull
callPerformEditorAction(int editorAction)659     public ImeCommand callPerformEditorAction(int editorAction) {
660         final Bundle params = new Bundle();
661         params.putInt("editorAction", editorAction);
662         return callCommandInternal("performEditorAction", params);
663     }
664 
665     /**
666      * Lets {@link MockIme} to call {@link InputConnection#performContextMenuAction(int)} with the
667      * given parameters.
668      *
669      * <p>This triggers {@code getCurrentInputConnection().performContextMenuAction(id)}.</p>
670      *
671      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
672      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
673      * value returned from the API.</p>
674      *
675      * @param id to be passed as the {@code id} parameter
676      * @return {@link ImeCommand} object that can be passed to
677      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
678      *         wait until this event is handled by {@link MockIme}
679      */
680     @NonNull
callPerformContextMenuAction(int id)681     public ImeCommand callPerformContextMenuAction(int id) {
682         final Bundle params = new Bundle();
683         params.putInt("id", id);
684         return callCommandInternal("performContextMenuAction", params);
685     }
686 
687     /**
688      * Lets {@link MockIme} to call {@link InputConnection#beginBatchEdit()} with the given
689      * parameters.
690      *
691      * <p>This triggers {@code getCurrentInputConnection().beginBatchEdit()}.</p>
692      *
693      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
694      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
695      * value returned from the API.</p>
696      *
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
callBeginBatchEdit()702     public ImeCommand callBeginBatchEdit() {
703         final Bundle params = new Bundle();
704         return callCommandInternal("beginBatchEdit", params);
705     }
706 
707     /**
708      * Lets {@link MockIme} to call {@link InputConnection#endBatchEdit()} with the given
709      * parameters.
710      *
711      * <p>This triggers {@code getCurrentInputConnection().endBatchEdit()}.</p>
712      *
713      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
714      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
715      * value returned from the API.</p>
716      *
717      * @return {@link ImeCommand} object that can be passed to
718      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
719      *         wait until this event is handled by {@link MockIme}
720      */
721     @NonNull
callEndBatchEdit()722     public ImeCommand callEndBatchEdit() {
723         final Bundle params = new Bundle();
724         return callCommandInternal("endBatchEdit", params);
725     }
726 
727     /**
728      * Lets {@link MockIme} to call {@link InputConnection#sendKeyEvent(KeyEvent)} with the given
729      * parameters.
730      *
731      * <p>This triggers {@code getCurrentInputConnection().sendKeyEvent(event)}.</p>
732      *
733      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
734      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
735      * value returned from the API.</p>
736      *
737      * @param event to be passed as the {@code event} parameter
738      * @return {@link ImeCommand} object that can be passed to
739      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
740      *         wait until this event is handled by {@link MockIme}
741      */
742     @NonNull
callSendKeyEvent(@ullable KeyEvent event)743     public ImeCommand callSendKeyEvent(@Nullable KeyEvent event) {
744         final Bundle params = new Bundle();
745         params.putParcelable("event", event);
746         return callCommandInternal("sendKeyEvent", params);
747     }
748 
749     /**
750      * Lets {@link MockIme} to call {@link InputConnection#clearMetaKeyStates(int)} with the given
751      * parameters.
752      *
753      * <p>This triggers {@code getCurrentInputConnection().sendKeyEvent(event)}.</p>
754      *
755      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
756      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
757      * value returned from the API.</p>
758      *
759      * @param states to be passed as the {@code states} parameter
760      * @return {@link ImeCommand} object that can be passed to
761      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
762      *         wait until this event is handled by {@link MockIme}
763      */
764     @NonNull
callClearMetaKeyStates(int states)765     public ImeCommand callClearMetaKeyStates(int states) {
766         final Bundle params = new Bundle();
767         params.putInt("states", states);
768         return callCommandInternal("clearMetaKeyStates", params);
769     }
770 
771     /**
772      * Lets {@link MockIme} to call {@link InputConnection#reportFullscreenMode(boolean)} with the
773      * given parameters.
774      *
775      * <p>This triggers {@code getCurrentInputConnection().reportFullscreenMode(enabled)}.</p>
776      *
777      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
778      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
779      * value returned from the API.</p>
780      *
781      * @param enabled to be passed as the {@code enabled} parameter
782      * @return {@link ImeCommand} object that can be passed to
783      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
784      *         wait until this event is handled by {@link MockIme}
785      */
786     @NonNull
callReportFullscreenMode(boolean enabled)787     public ImeCommand callReportFullscreenMode(boolean enabled) {
788         final Bundle params = new Bundle();
789         params.putBoolean("enabled", enabled);
790         return callCommandInternal("reportFullscreenMode", params);
791     }
792 
793     /**
794      * Lets {@link MockIme} to call {@link InputConnection#performPrivateCommand(String, Bundle)}
795      * with the given parameters.
796      *
797      * <p>This triggers {@code getCurrentInputConnection().performPrivateCommand(action, data)}.</p>
798      *
799      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
800      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
801      * value returned from the API.</p>
802      *
803      * @param action to be passed as the {@code action} parameter
804      * @param data to be passed as the {@code data} parameter
805      * @return {@link ImeCommand} object that can be passed to
806      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
807      *         wait until this event is handled by {@link MockIme}
808      */
809     @NonNull
callPerformPrivateCommand(@ullable String action, Bundle data)810     public ImeCommand callPerformPrivateCommand(@Nullable String action, Bundle data) {
811         final Bundle params = new Bundle();
812         params.putString("action", action);
813         params.putBundle("data", data);
814         return callCommandInternal("performPrivateCommand", params);
815     }
816 
817     /**
818      * Lets {@link MockIme} to call {@link InputConnection#requestCursorUpdates(int)} with the given
819      * parameters.
820      *
821      * <p>This triggers {@code getCurrentInputConnection().requestCursorUpdates(cursorUpdateMode)}.
822      * </p>
823      *
824      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
825      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
826      * value returned from the API.</p>
827      *
828      * @param cursorUpdateMode to be passed as the {@code cursorUpdateMode} 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
callRequestCursorUpdates(int cursorUpdateMode)834     public ImeCommand callRequestCursorUpdates(int cursorUpdateMode) {
835         final Bundle params = new Bundle();
836         params.putInt("cursorUpdateMode", cursorUpdateMode);
837         return callCommandInternal("requestCursorUpdates", params);
838     }
839 
840     /**
841      * Lets {@link MockIme} to call {@link InputConnection#getHandler()} with the given parameters.
842      *
843      * <p>This triggers {@code getCurrentInputConnection().getHandler()}.</p>
844      *
845      * <p>Use {@link ImeEvent#isNullReturnValue()} for {@link ImeEvent} returned from
846      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
847      * value returned from the API was {@code null} or not.</p>
848      *
849      * @return {@link ImeCommand} object that can be passed to
850      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
851      *         wait until this event is handled by {@link MockIme}
852      */
853     @NonNull
callGetHandler()854     public ImeCommand callGetHandler() {
855         final Bundle params = new Bundle();
856         return callCommandInternal("getHandler", params);
857     }
858 
859     /**
860      * Lets {@link MockIme} to call {@link InputConnection#closeConnection()} with the given
861      * parameters.
862      *
863      * <p>This triggers {@code getCurrentInputConnection().closeConnection()}.</p>
864      *
865      * <p>Return value information is not available for this command.</p>
866      *
867      * @return {@link ImeCommand} object that can be passed to
868      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
869      *         wait until this event is handled by {@link MockIme}
870      */
871     @NonNull
callCloseConnection()872     public ImeCommand callCloseConnection() {
873         final Bundle params = new Bundle();
874         return callCommandInternal("closeConnection", params);
875     }
876 
877     /**
878      * Lets {@link MockIme} to call
879      * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} with the given
880      * parameters.
881      *
882      * <p>This triggers
883      * {@code getCurrentInputConnection().commitContent(inputContentInfo, flags, opts)}.</p>
884      *
885      * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
886      * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
887      * value returned from the API.</p>
888      *
889      * @param inputContentInfo to be passed as the {@code inputContentInfo} parameter
890      * @param flags to be passed as the {@code flags} parameter
891      * @param opts to be passed as the {@code opts} parameter
892      * @return {@link ImeCommand} object that can be passed to
893      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
894      *         wait until this event is handled by {@link MockIme}
895      */
896     @NonNull
callCommitContent(@onNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts)897     public ImeCommand callCommitContent(@NonNull InputContentInfo inputContentInfo, int flags,
898             @Nullable Bundle opts) {
899         final Bundle params = new Bundle();
900         params.putParcelable("inputContentInfo", inputContentInfo);
901         params.putInt("flags", flags);
902         params.putBundle("opts", opts);
903         return callCommandInternal("commitContent", params);
904     }
905 
906     /**
907      * Lets {@link MockIme} to call
908      * {@link android.inputmethodservice.InputMethodService#setBackDisposition(int)} with the given
909      * parameters.
910      *
911      * <p>This triggers {@code setBackDisposition(backDisposition)}.</p>
912      *
913      * @param backDisposition to be passed as the {@code backDisposition} 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
callSetBackDisposition(int backDisposition)919     public ImeCommand callSetBackDisposition(int backDisposition) {
920         final Bundle params = new Bundle();
921         params.putInt("backDisposition", backDisposition);
922         return callCommandInternal("setBackDisposition", params);
923     }
924 
925     /**
926      * Lets {@link MockIme} to call
927      * {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)} with the given
928      * parameters.
929      *
930      * <p>This triggers {@code requestHideSelf(flags)}.</p>
931      *
932      * @param flags to be passed as the {@code flags} parameter
933      * @return {@link ImeCommand} object that can be passed to
934      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
935      *         wait until this event is handled by {@link MockIme}
936      */
937     @NonNull
callRequestHideSelf(int flags)938     public ImeCommand callRequestHideSelf(int flags) {
939         final Bundle params = new Bundle();
940         params.putInt("flags", flags);
941         return callCommandInternal("requestHideSelf", params);
942     }
943 
944     /**
945      * Lets {@link MockIme} to call
946      * {@link android.inputmethodservice.InputMethodService#requestShowSelf(int)} with the given
947      * parameters.
948      *
949      * <p>This triggers {@code requestShowSelf(flags)}.</p>
950      *
951      * @param flags to be passed as the {@code flags} parameter
952      * @return {@link ImeCommand} object that can be passed to
953      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
954      *         wait until this event is handled by {@link MockIme}
955      */
956     @NonNull
callRequestShowSelf(int flags)957     public ImeCommand callRequestShowSelf(int flags) {
958         final Bundle params = new Bundle();
959         params.putInt("flags", flags);
960         return callCommandInternal("requestShowSelf", params);
961     }
962 
963     /**
964      * Lets {@link MockIme} call
965      * {@link android.inputmethodservice.InputMethodService#sendDownUpKeyEvents(int)} with the given
966      * {@code keyEventCode}.
967      *
968      * @param keyEventCode to be passed as the {@code keyEventCode} parameter.
969      * @return {@link ImeCommand} object that can be passed to
970      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
971      *         wait until this event is handled by {@link MockIme}
972      */
973     @NonNull
callSendDownUpKeyEvents(int keyEventCode)974     public ImeCommand callSendDownUpKeyEvents(int keyEventCode) {
975         final Bundle params = new Bundle();
976         params.putInt("keyEventCode", keyEventCode);
977         return callCommandInternal("sendDownUpKeyEvents", params);
978     }
979 
980     /**
981      * Lets {@link MockIme} call
982      * {@link android.content.pm.PackageManager#getApplicationInfo(String, int)} with the given
983      * {@code packageName} and {@code flags}.
984      *
985      * @param packageName the package name to be passed to
986      *                    {@link android.content.pm.PackageManager#getApplicationInfo(String, int)}.
987      * @param flags the flags to be passed to
988      *                    {@link android.content.pm.PackageManager#getApplicationInfo(String, int)}.
989      * @return {@link ImeCommand} object that can be passed to
990      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
991      *         wait until this event is handled by {@link MockIme}.
992      */
993     @NonNull
callGetApplicationInfo(@onNull String packageName, int flags)994     public ImeCommand callGetApplicationInfo(@NonNull String packageName, int flags) {
995         final Bundle params = new Bundle();
996         params.putString("packageName", packageName);
997         params.putInt("flags", flags);
998         return callCommandInternal("getApplicationInfo", params);
999     }
1000 
1001     @NonNull
callGetDisplayId()1002     public ImeCommand callGetDisplayId() {
1003         final Bundle params = new Bundle();
1004         return callCommandInternal("getDisplayId", params);
1005     }
1006 
1007     /**
1008      * Verifies {@code InputMethodService.getLayoutInflater().getContext()} is equal to
1009      * {@code InputMethodService.this}.
1010      *
1011      * @return {@link ImeCommand} object that can be passed to
1012      *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
1013      *         wait until this event is handled by {@link MockIme}
1014      */
1015     @NonNull
verifyLayoutInflaterContext()1016     public ImeCommand verifyLayoutInflaterContext() {
1017         final Bundle params = new Bundle();
1018         return callCommandInternal("verifyLayoutInflaterContext", params);
1019     }
1020 
1021     @NonNull
callSetHeight(int height)1022     public ImeCommand callSetHeight(int height) {
1023         final Bundle params = new Bundle();
1024         params.putInt("height", height);
1025         return callCommandInternal("setHeight", params);
1026     }
1027 
1028     @NonNull
callSetInlineSuggestionsExtras(@onNull Bundle bundle)1029     public ImeCommand callSetInlineSuggestionsExtras(@NonNull Bundle bundle) {
1030         return callCommandInternal("setInlineSuggestionsExtras", bundle);
1031     }
1032 
1033     @NonNull
callVerifyGetDisplay()1034     public ImeCommand callVerifyGetDisplay() {
1035         return callCommandInternal("verifyGetDisplay", new Bundle());
1036     }
1037 
1038     @NonNull
callVerifyGetWindowManager()1039     public ImeCommand callVerifyGetWindowManager() {
1040         return callCommandInternal("verifyGetWindowManager", new Bundle());
1041     }
1042 
1043     @NonNull
callVerifyGetViewConfiguration()1044     public ImeCommand callVerifyGetViewConfiguration() {
1045         return callCommandInternal("verifyGetViewConfiguration", new Bundle());
1046     }
1047 }
1048