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