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