Home | History | Annotate | Download | only in mockime
      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 
     81         EventStore() {
     82             mArray = new ImeEvent[INITIAL_ARRAY_SIZE];
     83             mLength = 0;
     84         }
     85 
     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 
     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 
    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 
    117         MockImeEventReceiver(@NonNull String actionName) {
    118             mActionName = actionName;
    119         }
    120 
    121         @Override
    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 
    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 
    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
    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
    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 
    176     private ComponentName getMockImeComponentName() {
    177         return MockIme.getComponentName();
    178     }
    179 
    180     private String getMockImeId() {
    181         return MockIme.getImeId();
    182     }
    183 
    184     private MockImeSession(@NonNull Context context, @NonNull UiAutomation uiAutomation) {
    185         mContext = context;
    186         mUiAutomation = uiAutomation;
    187     }
    188 
    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
    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
    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      */
    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      */
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    966     public ImeCommand callGetDisplayId() {
    967         final Bundle params = new Bundle();
    968         return callCommandInternal("getDisplayId", params);
    969     }
    970 }
    971