1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.mockime; 18 19 import static android.server.wm.jetpack.extensions.util.ExtensionsUtil.getWindowExtensions; 20 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 21 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 22 import static android.view.WindowInsets.Type.captionBar; 23 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 24 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.content.res.Configuration; 29 import android.graphics.Bitmap; 30 import android.graphics.RectF; 31 import android.graphics.Region; 32 import android.inputmethodservice.ExtractEditText; 33 import android.inputmethodservice.InputMethodService; 34 import android.os.Bundle; 35 import android.os.CancellationSignal; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.IBinder; 39 import android.os.Looper; 40 import android.os.Process; 41 import android.os.ResultReceiver; 42 import android.os.StrictMode; 43 import android.os.SystemClock; 44 import android.util.Log; 45 import android.util.Size; 46 import android.util.TypedValue; 47 import android.view.Display; 48 import android.view.GestureDetector; 49 import android.view.Gravity; 50 import android.view.KeyEvent; 51 import android.view.MotionEvent; 52 import android.view.View; 53 import android.view.ViewConfiguration; 54 import android.view.ViewGroup; 55 import android.view.ViewParent; 56 import android.view.Window; 57 import android.view.WindowInsets; 58 import android.view.WindowManager; 59 import android.view.inputmethod.CancellableHandwritingGesture; 60 import android.view.inputmethod.CompletionInfo; 61 import android.view.inputmethod.CorrectionInfo; 62 import android.view.inputmethod.CursorAnchorInfo; 63 import android.view.inputmethod.EditorInfo; 64 import android.view.inputmethod.ExtractedTextRequest; 65 import android.view.inputmethod.Flags; 66 import android.view.inputmethod.HandwritingGesture; 67 import android.view.inputmethod.InlineSuggestion; 68 import android.view.inputmethod.InlineSuggestionsRequest; 69 import android.view.inputmethod.InlineSuggestionsResponse; 70 import android.view.inputmethod.InputBinding; 71 import android.view.inputmethod.InputConnection; 72 import android.view.inputmethod.InputContentInfo; 73 import android.view.inputmethod.InputMethod; 74 import android.view.inputmethod.InputMethodManager; 75 import android.view.inputmethod.InputMethodSubtype; 76 import android.view.inputmethod.PreviewableHandwritingGesture; 77 import android.view.inputmethod.TextAttribute; 78 import android.view.inputmethod.TextBoundsInfoResult; 79 import android.widget.Button; 80 import android.widget.FrameLayout; 81 import android.widget.HorizontalScrollView; 82 import android.widget.ImageView; 83 import android.widget.LinearLayout; 84 import android.widget.TextView; 85 import android.widget.inline.InlinePresentationSpec; 86 87 import androidx.annotation.AnyThread; 88 import androidx.annotation.CallSuper; 89 import androidx.annotation.MainThread; 90 import androidx.annotation.NonNull; 91 import androidx.annotation.Nullable; 92 import androidx.annotation.WorkerThread; 93 import androidx.autofill.inline.UiVersions; 94 import androidx.autofill.inline.UiVersions.StylesBuilder; 95 import androidx.autofill.inline.v1.InlineSuggestionUi; 96 import androidx.window.extensions.WindowExtensions; 97 import androidx.window.extensions.layout.WindowLayoutComponent; 98 import androidx.window.extensions.layout.WindowLayoutInfo; 99 100 import java.time.Duration; 101 import java.util.ArrayList; 102 import java.util.concurrent.ExecutorService; 103 import java.util.concurrent.Executors; 104 import java.util.concurrent.atomic.AtomicBoolean; 105 import java.util.concurrent.atomic.AtomicInteger; 106 import java.util.concurrent.atomic.AtomicReference; 107 import java.util.function.BooleanSupplier; 108 import java.util.function.Consumer; 109 import java.util.function.IntConsumer; 110 import java.util.function.Supplier; 111 112 /** 113 * Mock IME for end-to-end tests. 114 */ 115 public final class MockIme extends InputMethodService { 116 117 private static final String TAG = "MockIme"; 118 119 private static final long DELAY_CANCELLATION_SIGNAL_MILLIS = 500; 120 121 /** Default label for the custom extract text view. */ 122 public static final String CUSTOM_EXTRACT_EDIT_TEXT_LABEL = 123 "MockIme Custom Extract Edit Text Label"; 124 125 private ArrayList<MotionEvent> mEvents; 126 127 private View mExtractView; 128 129 @Nullable 130 private final WindowExtensions mWindowExtensions = getWindowExtensions(); 131 @Nullable 132 private final WindowLayoutComponent mWindowLayoutComponent = 133 mWindowExtensions != null ? mWindowExtensions.getWindowLayoutComponent() : null; 134 135 /** The extensions core Consumer to receive {@link WindowLayoutInfo} updates. */ 136 private androidx.window.extensions.core.util.function.Consumer<WindowLayoutInfo> 137 mWindowLayoutInfoConsumer; 138 139 private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver"); 140 141 private Handler mHandlerThreadHandler; 142 143 private final Handler mMainHandler = new Handler(); 144 145 private final Consumer<Bundle> mCommandHandler = (bundle) -> { 146 mHandlerThreadHandler.post(() -> { 147 onReceiveCommand(ImeCommand.fromBundle(bundle)); 148 }); 149 }; 150 151 private final Configuration mLastDispatchedConfiguration = new Configuration(); 152 153 @Nullable 154 private InputConnection mMemorizedInputConnection = null; 155 156 @Nullable 157 @MainThread getMemorizedOrCurrentInputConnection()158 private InputConnection getMemorizedOrCurrentInputConnection() { 159 return mMemorizedInputConnection != null 160 ? mMemorizedInputConnection : getCurrentInputConnection(); 161 } 162 163 @WorkerThread onReceiveCommand(@onNull ImeCommand command)164 private void onReceiveCommand(@NonNull ImeCommand command) { 165 getTracer().onReceiveCommand(command, () -> { 166 if (command.shouldDispatchToMainThread()) { 167 mMainHandler.post(() -> onHandleCommand(command)); 168 } else { 169 onHandleCommand(command); 170 } 171 }); 172 } 173 174 @AnyThread onHandleCommand(@onNull ImeCommand command)175 private void onHandleCommand(@NonNull ImeCommand command) { 176 getTracer().onHandleCommand(command, () -> { 177 if (command.shouldDispatchToMainThread()) { 178 if (Looper.myLooper() != Looper.getMainLooper()) { 179 throw new IllegalStateException("command " + command 180 + " should be handled on the main thread"); 181 } 182 // The context which created from InputMethodService#createXXXContext must behave 183 // like an UI context, which can obtain a display, a window manager, 184 // a view configuration and a gesture detector instance without strict mode 185 // violation. 186 final Configuration testConfig = new Configuration(); 187 testConfig.setToDefaults(); 188 final Context configContext = createConfigurationContext(testConfig); 189 final Context attrContext = createAttributionContext(null /* attributionTag */); 190 // UI component accesses on a display context must throw strict mode violations. 191 final Context displayContext = createDisplayContext(getDisplay()); 192 switch (command.getName()) { 193 case "suspendCreateSession": { 194 if (!Looper.getMainLooper().isCurrentThread()) { 195 return new UnsupportedOperationException("suspendCreateSession can be" 196 + " requested only for the main thread."); 197 } 198 mSuspendCreateSession = true; 199 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 200 } 201 case "resumeCreateSession": { 202 if (!Looper.getMainLooper().isCurrentThread()) { 203 return new UnsupportedOperationException("resumeCreateSession can be" 204 + " requested only for the main thread."); 205 } 206 if (mSuspendedCreateSession != null) { 207 mSuspendedCreateSession.run(); 208 mSuspendedCreateSession = null; 209 } 210 mSuspendCreateSession = false; 211 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 212 } 213 case "memorizeCurrentInputConnection": { 214 if (!Looper.getMainLooper().isCurrentThread()) { 215 return new UnsupportedOperationException( 216 "memorizeCurrentInputConnection can be requested only for the" 217 + " main thread."); 218 } 219 mMemorizedInputConnection = getCurrentInputConnection(); 220 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 221 } 222 case "unmemorizeCurrentInputConnection": { 223 if (!Looper.getMainLooper().isCurrentThread()) { 224 return new UnsupportedOperationException( 225 "unmemorizeCurrentInputConnection can be requested only for the" 226 + " main thread."); 227 } 228 mMemorizedInputConnection = null; 229 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 230 } 231 case "getTextBeforeCursor": { 232 final int n = command.getExtras().getInt("n"); 233 final int flag = command.getExtras().getInt("flag"); 234 return getMemorizedOrCurrentInputConnection().getTextBeforeCursor(n, flag); 235 } 236 case "getTextAfterCursor": { 237 final int n = command.getExtras().getInt("n"); 238 final int flag = command.getExtras().getInt("flag"); 239 return getMemorizedOrCurrentInputConnection().getTextAfterCursor(n, flag); 240 } 241 case "getSelectedText": { 242 final int flag = command.getExtras().getInt("flag"); 243 return getMemorizedOrCurrentInputConnection().getSelectedText(flag); 244 } 245 case "getSurroundingText": { 246 final int beforeLength = command.getExtras().getInt("beforeLength"); 247 final int afterLength = command.getExtras().getInt("afterLength"); 248 final int flags = command.getExtras().getInt("flags"); 249 return getMemorizedOrCurrentInputConnection().getSurroundingText( 250 beforeLength, afterLength, flags); 251 } 252 case "getCursorCapsMode": { 253 final int reqModes = command.getExtras().getInt("reqModes"); 254 return getMemorizedOrCurrentInputConnection().getCursorCapsMode(reqModes); 255 } 256 case "getExtractedText": { 257 final ExtractedTextRequest request = 258 command.getExtras().getParcelable("request"); 259 final int flags = command.getExtras().getInt("flags"); 260 return getMemorizedOrCurrentInputConnection().getExtractedText(request, 261 flags); 262 } 263 case "deleteSurroundingText": { 264 final int beforeLength = command.getExtras().getInt("beforeLength"); 265 final int afterLength = command.getExtras().getInt("afterLength"); 266 return getMemorizedOrCurrentInputConnection().deleteSurroundingText( 267 beforeLength, afterLength); 268 } 269 case "deleteSurroundingTextInCodePoints": { 270 final int beforeLength = command.getExtras().getInt("beforeLength"); 271 final int afterLength = command.getExtras().getInt("afterLength"); 272 return getMemorizedOrCurrentInputConnection() 273 .deleteSurroundingTextInCodePoints(beforeLength, afterLength); 274 } 275 case "setComposingText(CharSequence,int)": { 276 final CharSequence text = command.getExtras().getCharSequence("text"); 277 final int newCursorPosition = 278 command.getExtras().getInt("newCursorPosition"); 279 return getMemorizedOrCurrentInputConnection().setComposingText( 280 text, newCursorPosition); 281 } 282 case "setComposingText(CharSequence,int,TextAttribute)": { 283 final CharSequence text = command.getExtras().getCharSequence("text"); 284 final int newCursorPosition = 285 command.getExtras().getInt("newCursorPosition"); 286 final TextAttribute textAttribute = 287 command.getExtras().getParcelable("textAttribute"); 288 return getMemorizedOrCurrentInputConnection() 289 .setComposingText(text, newCursorPosition, textAttribute); 290 } 291 case "setComposingRegion(int,int)": { 292 final int start = command.getExtras().getInt("start"); 293 final int end = command.getExtras().getInt("end"); 294 return getMemorizedOrCurrentInputConnection().setComposingRegion(start, 295 end); 296 } 297 case "setComposingRegion(int,int,TextAttribute)": { 298 final int start = command.getExtras().getInt("start"); 299 final int end = command.getExtras().getInt("end"); 300 final TextAttribute textAttribute = 301 command.getExtras().getParcelable("textAttribute"); 302 return getMemorizedOrCurrentInputConnection() 303 .setComposingRegion(start, end, textAttribute); 304 } 305 case "finishComposingText": 306 return getMemorizedOrCurrentInputConnection().finishComposingText(); 307 case "commitText(CharSequence,int)": { 308 final CharSequence text = command.getExtras().getCharSequence("text"); 309 final int newCursorPosition = 310 command.getExtras().getInt("newCursorPosition"); 311 return getMemorizedOrCurrentInputConnection().commitText(text, 312 newCursorPosition); 313 } 314 case "commitText(CharSequence,int,TextAttribute)": { 315 final CharSequence text = command.getExtras().getCharSequence("text"); 316 final int newCursorPosition = 317 command.getExtras().getInt("newCursorPosition"); 318 final TextAttribute textAttribute = 319 command.getExtras().getParcelable("textAttribute"); 320 return getMemorizedOrCurrentInputConnection() 321 .commitText(text, newCursorPosition, textAttribute); 322 } 323 case "commitCompletion": { 324 final CompletionInfo text = command.getExtras().getParcelable("text"); 325 return getMemorizedOrCurrentInputConnection().commitCompletion(text); 326 } 327 case "commitCorrection": { 328 final CorrectionInfo correctionInfo = 329 command.getExtras().getParcelable("correctionInfo"); 330 return getMemorizedOrCurrentInputConnection().commitCorrection( 331 correctionInfo); 332 } 333 case "setSelection": { 334 final int start = command.getExtras().getInt("start"); 335 final int end = command.getExtras().getInt("end"); 336 return getMemorizedOrCurrentInputConnection().setSelection(start, end); 337 } 338 case "performEditorAction": { 339 final int editorAction = command.getExtras().getInt("editorAction"); 340 return getMemorizedOrCurrentInputConnection().performEditorAction( 341 editorAction); 342 } 343 case "performContextMenuAction": { 344 final int id = command.getExtras().getInt("id"); 345 return getMemorizedOrCurrentInputConnection().performContextMenuAction(id); 346 } 347 case "beginBatchEdit": 348 return getMemorizedOrCurrentInputConnection().beginBatchEdit(); 349 case "endBatchEdit": 350 return getMemorizedOrCurrentInputConnection().endBatchEdit(); 351 case "sendKeyEvent": { 352 final KeyEvent event = command.getExtras().getParcelable("event"); 353 return getMemorizedOrCurrentInputConnection().sendKeyEvent(event); 354 } 355 case "clearMetaKeyStates": { 356 final int states = command.getExtras().getInt("states"); 357 return getMemorizedOrCurrentInputConnection().clearMetaKeyStates(states); 358 } 359 case "reportFullscreenMode": { 360 final boolean enabled = command.getExtras().getBoolean("enabled"); 361 return getMemorizedOrCurrentInputConnection().reportFullscreenMode(enabled); 362 } 363 case "getOnEvaluateFullscreenMode": { 364 return onEvaluateFullscreenMode(); 365 } 366 case "performSpellCheck": { 367 return getMemorizedOrCurrentInputConnection().performSpellCheck(); 368 } 369 case "takeSnapshot": { 370 return getMemorizedOrCurrentInputConnection().takeSnapshot(); 371 } 372 case "performPrivateCommand": { 373 final String action = command.getExtras().getString("action"); 374 final Bundle data = command.getExtras().getBundle("data"); 375 return getMemorizedOrCurrentInputConnection().performPrivateCommand(action, 376 data); 377 } 378 case "previewHandwritingGesture": { 379 final PreviewableHandwritingGesture gesture = 380 (PreviewableHandwritingGesture) HandwritingGesture.fromByteArray( 381 command.getExtras().getByteArray("gesture")); 382 383 boolean useDelayedCancellation = 384 command.getExtras().getBoolean("useDelayedCancellation"); 385 final CancellationSignal cs = 386 useDelayedCancellation ? new CancellationSignal() : null; 387 if (useDelayedCancellation) { 388 new Handler().postDelayed(() -> cs.cancel(), 389 DELAY_CANCELLATION_SIGNAL_MILLIS); 390 } 391 392 getMemorizedOrCurrentInputConnection().previewHandwritingGesture( 393 gesture, cs); 394 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 395 } 396 case "performHandwritingGesture": { 397 final HandwritingGesture gesture = 398 HandwritingGesture.fromByteArray( 399 command.getExtras().getByteArray("gesture")); 400 401 boolean useDelayedCancellation = 402 command.getExtras().getBoolean("useDelayedCancellation"); 403 if (useDelayedCancellation 404 && gesture instanceof CancellableHandwritingGesture) { 405 final CancellationSignal cs = new CancellationSignal(); 406 ((CancellableHandwritingGesture) gesture).setCancellationSignal(cs); 407 new Handler().postDelayed(() -> cs.cancel(), 408 DELAY_CANCELLATION_SIGNAL_MILLIS); 409 } 410 411 IntConsumer consumer = value -> 412 getTracer().onPerformHandwritingGestureResult( 413 value, command.getId(), () -> {}); 414 getMemorizedOrCurrentInputConnection() 415 .performHandwritingGesture(gesture, mMainHandler::post, consumer); 416 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 417 } 418 case "requestTextBoundsInfo": { 419 var rectF = command.getExtras().getParcelable("rectF", RectF.class); 420 Consumer<TextBoundsInfoResult> consumer = value -> 421 getTracer().onRequestTextBoundsInfoResult( 422 value, command.getId()); 423 getMemorizedOrCurrentInputConnection().requestTextBoundsInfo( 424 rectF, mMainHandler::post, consumer); 425 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 426 } 427 case "requestCursorUpdates": { 428 final int cursorUpdateMode = command.getExtras().getInt("cursorUpdateMode"); 429 final int cursorUpdateFilter = 430 command.getExtras().getInt("cursorUpdateFilter", 0); 431 return getMemorizedOrCurrentInputConnection().requestCursorUpdates( 432 cursorUpdateMode, cursorUpdateFilter); 433 } 434 case "getHandler": 435 return getMemorizedOrCurrentInputConnection().getHandler(); 436 case "closeConnection": 437 getMemorizedOrCurrentInputConnection().closeConnection(); 438 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 439 case "commitContent": { 440 final InputContentInfo inputContentInfo = 441 command.getExtras().getParcelable("inputContentInfo"); 442 final int flags = command.getExtras().getInt("flags"); 443 final Bundle opts = command.getExtras().getBundle("opts"); 444 return getMemorizedOrCurrentInputConnection().commitContent( 445 inputContentInfo, flags, opts); 446 } 447 case "setImeConsumesInput": { 448 final boolean imeConsumesInput = 449 command.getExtras().getBoolean("imeConsumesInput"); 450 return getMemorizedOrCurrentInputConnection().setImeConsumesInput( 451 imeConsumesInput); 452 } 453 case "replaceText": { 454 final int start = command.getExtras().getInt("start"); 455 final int end = command.getExtras().getInt("end"); 456 final CharSequence text = command.getExtras().getCharSequence("text"); 457 final int newCursorPosition = 458 command.getExtras().getInt("newCursorPosition"); 459 final TextAttribute textAttribute = 460 command.getExtras().getParcelable("textAttribute"); 461 return getMemorizedOrCurrentInputConnection() 462 .replaceText(start, end, text, newCursorPosition, textAttribute); 463 } 464 case "setExplicitlyEnabledInputMethodSubtypes": { 465 final String imeId = command.getExtras().getString("imeId"); 466 final int[] subtypeHashCodes = 467 command.getExtras().getIntArray("subtypeHashCodes"); 468 getSystemService(InputMethodManager.class) 469 .setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes); 470 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 471 } 472 case "setAdditionalInputMethodSubtypes": { 473 final String imeId = command.getExtras().getString("imeId"); 474 final InputMethodSubtype[] subtypes = 475 command.getExtras().getParcelableArray("subtypes", 476 InputMethodSubtype.class); 477 getSystemService(InputMethodManager.class) 478 .setAdditionalInputMethodSubtypes(imeId, subtypes); 479 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 480 } 481 case "switchInputMethod": { 482 final String id = command.getExtras().getString("id"); 483 try { 484 switchInputMethod(id); 485 } catch (Exception e) { 486 return e; 487 } 488 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 489 } 490 case "switchInputMethod(String,InputMethodSubtype)": { 491 final String id = command.getExtras().getString("id"); 492 final InputMethodSubtype subtype = command.getExtras().getParcelable( 493 "subtype", InputMethodSubtype.class); 494 try { 495 switchInputMethod(id, subtype); 496 } catch (Exception e) { 497 return e; 498 } 499 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 500 } 501 case "setBackDisposition": { 502 final int backDisposition = 503 command.getExtras().getInt("backDisposition"); 504 setBackDisposition(backDisposition); 505 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 506 } 507 case "requestHideSelf": { 508 final int flags = command.getExtras().getInt("flags"); 509 requestHideSelf(flags); 510 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 511 } 512 case "requestShowSelf": { 513 final int flags = command.getExtras().getInt("flags"); 514 requestShowSelf(flags); 515 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 516 } 517 case "sendDownUpKeyEvents": { 518 final int keyEventCode = command.getExtras().getInt("keyEventCode"); 519 sendDownUpKeyEvents(keyEventCode); 520 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 521 } 522 case "getApplicationInfo": { 523 final String packageName = command.getExtras().getString("packageName"); 524 final int flags = command.getExtras().getInt("flags"); 525 try { 526 return getPackageManager().getApplicationInfo(packageName, flags); 527 } catch (PackageManager.NameNotFoundException e) { 528 return e; 529 } 530 } 531 case "getDisplayId": 532 return getDisplay().getDisplayId(); 533 case "verifyLayoutInflaterContext": 534 return getLayoutInflater().getContext() == this; 535 case "setHeight": 536 final int height = command.getExtras().getInt("height"); 537 mView.setHeight(height); 538 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 539 case "setExtractView": 540 final String label = command.getExtras().getString("label"); 541 setExtractView(createCustomExtractTextView(label)); 542 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 543 case "verifyExtractViewNotNull": 544 if (mExtractView == null) { 545 return false; 546 } else { 547 return mExtractView.findViewById(android.R.id.inputExtractAction) 548 != null 549 && mExtractView.findViewById( 550 android.R.id.inputExtractAccessories) != null 551 && mExtractView.findViewById( 552 android.R.id.inputExtractEditText) != null; 553 } 554 case "verifyGetDisplay": 555 try { 556 return verifyGetDisplay(); 557 } catch (UnsupportedOperationException e) { 558 return e; 559 } 560 case "verifyIsUiContext": 561 return verifyIsUiContext(); 562 case "verifyGetWindowManager": { 563 final WindowManager imsWm = getSystemService(WindowManager.class); 564 final WindowManager configContextWm = 565 configContext.getSystemService(WindowManager.class); 566 final WindowManager attrContextWm = 567 attrContext.getSystemService(WindowManager.class); 568 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 569 } 570 case "verifyGetViewConfiguration": { 571 final ViewConfiguration imsViewConfig = ViewConfiguration.get(this); 572 final ViewConfiguration configContextViewConfig = 573 ViewConfiguration.get(configContext); 574 final ViewConfiguration attrContextViewConfig = 575 ViewConfiguration.get(attrContext); 576 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 577 } 578 case "verifyGetGestureDetector": { 579 GestureDetector.SimpleOnGestureListener listener = 580 new GestureDetector.SimpleOnGestureListener(); 581 final GestureDetector imsGestureDetector = 582 new GestureDetector(this, listener); 583 final GestureDetector configContextGestureDetector = 584 new GestureDetector(configContext, listener); 585 final GestureDetector attrGestureDetector = 586 new GestureDetector(attrContext, listener); 587 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 588 } 589 case "verifyGetWindowManagerOnDisplayContext": { 590 // Obtaining a WindowManager on a display context must throw a strict mode 591 // violation. 592 final WindowManager wm = displayContext 593 .getSystemService(WindowManager.class); 594 595 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 596 } 597 case "verifyGetViewConfigurationOnDisplayContext": { 598 // Obtaining a ViewConfiguration on a display context must throw a strict 599 // mode violation. 600 final ViewConfiguration viewConfiguration = 601 ViewConfiguration.get(displayContext); 602 603 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 604 } 605 case "verifyGetGestureDetectorOnDisplayContext": { 606 // Obtaining a GestureDetector on a display context must throw a strict mode 607 // violation. 608 GestureDetector.SimpleOnGestureListener listener = 609 new GestureDetector.SimpleOnGestureListener(); 610 final GestureDetector gestureDetector = 611 new GestureDetector(displayContext, listener); 612 613 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 614 } 615 case "getStylusHandwritingWindowVisibility": { 616 if (getStylusHandwritingWindow() == null) { 617 return false; 618 } 619 View decorView = getStylusHandwritingWindow().getDecorView(); 620 return decorView != null && decorView.isAttachedToWindow() 621 && decorView.getVisibility() == View.VISIBLE; 622 } 623 case "hasStylusHandwritingWindow": { 624 return getStylusHandwritingWindow() != null; 625 } 626 case "setStylusHandwritingInkView": { 627 View inkView = new View(attrContext); 628 getStylusHandwritingWindow().setContentView(inkView); 629 mEvents = new ArrayList<>(); 630 inkView.setOnTouchListener((view, event) -> 631 mEvents.add(MotionEvent.obtain(event))); 632 return true; 633 } 634 case "setStylusHandwritingTimeout": { 635 setStylusHandwritingSessionTimeout( 636 Duration.ofMillis(command.getExtras().getLong("timeoutMs"))); 637 return true; 638 } 639 case "getStylusHandwritingTimeout": { 640 return getStylusHandwritingSessionTimeout().toMillis(); 641 } 642 case "getStylusHandwritingEvents": { 643 return mEvents; 644 } 645 case "setStylusHandwritingRegion": { 646 Region handwritingRegion = command.getExtras().getParcelable( 647 "handwritingRegion", Region.class); 648 setStylusHandwritingRegion(handwritingRegion); 649 return true; 650 } 651 case "finishStylusHandwriting": { 652 finishStylusHandwriting(); 653 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 654 } 655 case "finishConnectionlessStylusHandwriting": { 656 finishConnectionlessStylusHandwriting( 657 command.getExtras().getCharSequence("text")); 658 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 659 } 660 case "getCurrentWindowMetricsBounds": { 661 return getSystemService(WindowManager.class) 662 .getCurrentWindowMetrics().getBounds(); 663 } 664 case "setImeCaptionBarVisible": { 665 final boolean visible = command.getExtras().getBoolean("visible"); 666 if (visible) { 667 mView.getRootView().getWindowInsetsController().show(captionBar()); 668 } else { 669 mView.getRootView().getWindowInsetsController().hide(captionBar()); 670 } 671 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 672 } 673 case "getImeCaptionBarHeight": { 674 return mView.getRootWindowInsets().getInsets(captionBar()).bottom; 675 } 676 } 677 } 678 return ImeEvent.RETURN_VALUE_UNAVAILABLE; 679 }); 680 } 681 verifyGetDisplay()682 private boolean verifyGetDisplay() throws UnsupportedOperationException { 683 final Display display; 684 final Display configContextDisplay; 685 final Configuration config = new Configuration(); 686 config.setToDefaults(); 687 final Context configContext = createConfigurationContext(config); 688 display = getDisplay(); 689 configContextDisplay = configContext.getDisplay(); 690 return display != null && configContextDisplay != null; 691 } 692 verifyIsUiContext()693 private boolean verifyIsUiContext() { 694 final Configuration config = new Configuration(); 695 config.setToDefaults(); 696 final Context configContext = createConfigurationContext(config); 697 // The value must be true because ConfigurationContext is derived from InputMethodService, 698 // which is a UI Context. 699 final boolean imeDerivedConfigContext = configContext.isUiContext(); 700 // The value must be false because DisplayContext won't receive any config update from 701 // server. 702 final boolean imeDerivedDisplayContext = createDisplayContext(getDisplay()).isUiContext(); 703 return isUiContext() && imeDerivedConfigContext && !imeDerivedDisplayContext; 704 } 705 706 @Nullable 707 private ImeSettings mSettings; 708 709 private final AtomicReference<String> mClientPackageName = new AtomicReference<>(); 710 711 @Nullable getClientPackageName()712 String getClientPackageName() { 713 return mClientPackageName.get(); 714 } 715 716 private boolean mDestroying; 717 718 /** 719 * {@code true} if {@link MockInputMethodImpl#createSession(InputMethod.SessionCallback)} 720 * needs to be suspended. 721 * 722 * <p>Must be touched from the main thread.</p> 723 */ 724 private boolean mSuspendCreateSession = false; 725 726 /** 727 * The suspended {@link MockInputMethodImpl#createSession(InputMethod.SessionCallback)} callback 728 * operation. 729 * 730 * <p>Must be touched from the main thread.</p> 731 */ 732 @Nullable 733 Runnable mSuspendedCreateSession; 734 735 private class MockInputMethodImpl extends InputMethodImpl { 736 @Override showSoftInput(int flags, ResultReceiver resultReceiver)737 public void showSoftInput(int flags, ResultReceiver resultReceiver) { 738 getTracer().showSoftInput(flags, resultReceiver, 739 () -> super.showSoftInput(flags, resultReceiver)); 740 } 741 742 @Override hideSoftInput(int flags, ResultReceiver resultReceiver)743 public void hideSoftInput(int flags, ResultReceiver resultReceiver) { 744 getTracer().hideSoftInput(flags, resultReceiver, 745 () -> super.hideSoftInput(flags, resultReceiver)); 746 } 747 748 @Override createSession(SessionCallback callback)749 public void createSession(SessionCallback callback) { 750 getTracer().createSession(() -> { 751 if (mSuspendCreateSession) { 752 if (mSuspendedCreateSession != null) { 753 throw new IllegalStateException("suspendCreateSession() must be " 754 + "called before receiving another createSession()"); 755 } 756 mSuspendedCreateSession = () -> { 757 if (!mDestroying) { 758 super.createSession(callback); 759 } 760 }; 761 } else { 762 super.createSession(callback); 763 } 764 }); 765 } 766 767 @Override attachToken(IBinder token)768 public void attachToken(IBinder token) { 769 getTracer().attachToken(token, () -> super.attachToken(token)); 770 } 771 772 @Override bindInput(InputBinding binding)773 public void bindInput(InputBinding binding) { 774 getTracer().bindInput(binding, () -> super.bindInput(binding)); 775 } 776 777 @Override unbindInput()778 public void unbindInput() { 779 getTracer().unbindInput(() -> super.unbindInput()); 780 } 781 } 782 783 @Override onCreate()784 public void onCreate() { 785 // Initialize minimum settings to send events in Tracer#onCreate(). 786 mSettings = SettingsProvider.getSettings(); 787 if (mSettings == null) { 788 throw new IllegalStateException("Settings file is not found. " 789 + "Make sure MockImeSession.create() is used to launch Mock IME."); 790 } 791 mClientPackageName.set(mSettings.getClientPackageName()); 792 793 // TODO(b/159593676): consider to detect more violations 794 if (mSettings.isStrictModeEnabled()) { 795 StrictMode.setVmPolicy( 796 new StrictMode.VmPolicy.Builder() 797 .detectIncorrectContextUse() 798 .penaltyLog() 799 .penaltyListener(Runnable::run, 800 v -> getTracer().onStrictModeViolated(() -> { })) 801 .build()); 802 } 803 804 if (mSettings.isOnBackCallbackEnabled()) { 805 getApplicationInfo().setEnableOnBackInvokedCallback(true); 806 } 807 808 getTracer().onCreate(() -> { 809 super.onCreate(); 810 mHandlerThread.start(); 811 mHandlerThreadHandler = new Handler(mHandlerThread.getLooper()); 812 mSettings.getChannel().registerListener(mCommandHandler); 813 // If Extension components are not loaded successfully, notify Test app. 814 if (mSettings.isWindowLayoutInfoCallbackEnabled()) { 815 getTracer().onVerify("windowLayoutComponentLoaded", 816 () -> mWindowLayoutComponent != null); 817 } 818 if (mSettings.isVerifyContextApisInOnCreate()) { 819 getTracer().onVerify("isUiContext", this::verifyIsUiContext); 820 getTracer().onVerify("getDisplay", this::verifyGetDisplay); 821 } 822 823 // Ensure the window height is tall enough to receive system window insets. 824 final FrameLayout windowSizeEnsurer = new FrameLayout(this); 825 windowSizeEnsurer.setFitsSystemWindows(true); 826 windowSizeEnsurer.setWillNotDraw(true); 827 final ViewGroup decorView = (ViewGroup) getWindow().getWindow().getDecorView(); 828 decorView.addView(windowSizeEnsurer, new ViewGroup.LayoutParams( 829 MATCH_PARENT, WRAP_CONTENT)); 830 831 final int windowFlags = mSettings.getWindowFlags(0); 832 final int windowFlagsMask = mSettings.getWindowFlagsMask(0); 833 if (windowFlags != 0 || windowFlagsMask != 0) { 834 final int prevFlags = getWindow().getWindow().getAttributes().flags; 835 getWindow().getWindow().setFlags(windowFlags, windowFlagsMask); 836 // For some reasons, seems that we need to post another requestLayout() when 837 // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS bit is changed. 838 // TODO: Investigate the reason. 839 if ((windowFlagsMask & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) { 840 final boolean hadFlag = (prevFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; 841 final boolean hasFlag = (windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; 842 if (hadFlag != hasFlag) { 843 decorView.post(() -> decorView.requestLayout()); 844 } 845 } 846 } 847 848 // Ensuring bar contrast interferes with the tests. 849 getWindow().getWindow().setStatusBarContrastEnforced(false); 850 getWindow().getWindow().setNavigationBarContrastEnforced(false); 851 852 if (mSettings.hasNavigationBarColor()) { 853 getWindow().getWindow().setNavigationBarColor(mSettings.getNavigationBarColor()); 854 } 855 856 // Initialize to current Configuration to prevent unexpected configDiff value dispatched 857 // in IME event. 858 mLastDispatchedConfiguration.setTo(getResources().getConfiguration()); 859 }); 860 861 if (mSettings.isWindowLayoutInfoCallbackEnabled() && mWindowLayoutComponent != null) { 862 mWindowLayoutInfoConsumer = (windowLayoutInfo) -> getTracer().getWindowLayoutInfo( 863 windowLayoutInfo, () -> {}); 864 mWindowLayoutComponent.addWindowLayoutInfoListener(this, mWindowLayoutInfoConsumer); 865 } 866 } 867 868 @Override onCurrentInputMethodSubtypeChanged(InputMethodSubtype newSubtype)869 protected void onCurrentInputMethodSubtypeChanged(InputMethodSubtype newSubtype) { 870 getTracer().onCurrentInputMethodSubtypeChanged(newSubtype, 871 () -> super.onCurrentInputMethodSubtypeChanged(newSubtype)); 872 } 873 874 @Override onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly)875 public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) { 876 getTracer().onConfigureWindow(win, isFullscreen, isCandidatesOnly, 877 () -> super.onConfigureWindow(win, isFullscreen, isCandidatesOnly)); 878 } 879 880 @Override onEvaluateFullscreenMode()881 public boolean onEvaluateFullscreenMode() { 882 return getTracer().onEvaluateFullscreenMode(() -> { 883 final int policy = mSettings.fullscreenModePolicy(); 884 switch (policy) { 885 case ImeSettings.FullscreenModePolicy.NO_FULLSCREEN: 886 return false; 887 case ImeSettings.FullscreenModePolicy.FORCE_FULLSCREEN: 888 return true; 889 case ImeSettings.FullscreenModePolicy.OS_DEFAULT: 890 return super.onEvaluateFullscreenMode(); 891 default: 892 Log.e(TAG, "unknown FullscreenModePolicy=" + policy); 893 return false; 894 } 895 }); 896 } 897 898 @Override onCreateExtractTextView()899 public View onCreateExtractTextView() { 900 if (mSettings != null && mSettings.isCustomExtractTextViewEnabled()) { 901 mExtractView = createCustomExtractTextView(CUSTOM_EXTRACT_EDIT_TEXT_LABEL); 902 } else { 903 mExtractView = super.onCreateExtractTextView(); 904 } 905 return mExtractView; 906 } 907 createCustomExtractTextView(String label)908 private View createCustomExtractTextView(String label) { 909 LinearLayout container = new LinearLayout(this); 910 container.setOrientation(LinearLayout.VERTICAL); 911 912 TextView labelView = new TextView(this); 913 labelView.setText(label); 914 container.addView(labelView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); 915 916 // Using a subclass of ExtractEditText should be allowed. 917 ExtractEditText extractEditText = new ExtractEditText(this) {}; 918 Log.d(TAG, "Using custom ExtractEditText: " + extractEditText); 919 extractEditText.setId(android.R.id.inputExtractEditText); 920 container.addView(extractEditText, new LinearLayout.LayoutParams( 921 MATCH_PARENT, 0 /* height */, 1f /* weight */ 922 )); 923 924 FrameLayout accessories = new FrameLayout(this); 925 accessories.setId(android.R.id.inputExtractAccessories); 926 container.addView(accessories, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); 927 928 Button actionButton = new Button(this); 929 actionButton.setId(android.R.id.inputExtractAction); 930 actionButton.setText("inputExtractAction"); 931 accessories.addView(actionButton, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); 932 933 return container; 934 } 935 936 private static final class KeyboardLayoutView extends LinearLayout { 937 @NonNull 938 private final MockIme mMockIme; 939 @NonNull 940 private final ImeSettings mSettings; 941 @NonNull 942 private final View.OnLayoutChangeListener mLayoutListener; 943 944 private final LinearLayout mLayout; 945 946 @Nullable 947 private final LinearLayout mSuggestionView; 948 949 private boolean mDrawsBehindNavBar = false; 950 KeyboardLayoutView(MockIme mockIme, @NonNull ImeSettings imeSettings, @Nullable Consumer<ImeLayoutInfo> onInputViewLayoutChangedCallback)951 KeyboardLayoutView(MockIme mockIme, @NonNull ImeSettings imeSettings, 952 @Nullable Consumer<ImeLayoutInfo> onInputViewLayoutChangedCallback) { 953 super(mockIme); 954 955 mMockIme = mockIme; 956 mSettings = imeSettings; 957 958 setOrientation(VERTICAL); 959 960 final int defaultBackgroundColor = 961 getResources().getColor(android.R.color.holo_orange_dark, null); 962 963 final int mainSpacerHeight = mSettings.getInputViewHeight(LayoutParams.WRAP_CONTENT); 964 mLayout = new LinearLayout(getContext()); 965 mLayout.setOrientation(LinearLayout.VERTICAL); 966 967 if (mSettings.getInlineSuggestionsEnabled()) { 968 final HorizontalScrollView scrollView = new HorizontalScrollView(getContext()); 969 final LayoutParams scrollViewParams = new LayoutParams(MATCH_PARENT, 100); 970 scrollView.setLayoutParams(scrollViewParams); 971 972 final LinearLayout suggestionView = new LinearLayout(getContext()); 973 suggestionView.setBackgroundColor(0xFFEEEEEE); 974 final String suggestionViewContentDesc = 975 mSettings.getInlineSuggestionViewContentDesc(null /* default */); 976 if (suggestionViewContentDesc != null) { 977 suggestionView.setContentDescription(suggestionViewContentDesc); 978 } 979 scrollView.addView(suggestionView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 980 mSuggestionView = suggestionView; 981 982 mLayout.addView(scrollView); 983 } else { 984 mSuggestionView = null; 985 } 986 987 { 988 final FrameLayout secondaryLayout = new FrameLayout(getContext()); 989 secondaryLayout.setForegroundGravity(Gravity.CENTER); 990 991 final TextView textView = new TextView(getContext()); 992 textView.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT)); 993 textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); 994 textView.setGravity(Gravity.CENTER); 995 textView.setText( 996 new ComponentName(mMockIme.getApplicationContext().getPackageName(), 997 MockIme.class.getName()).flattenToShortString()); 998 textView.setBackgroundColor( 999 mSettings.getBackgroundColor(defaultBackgroundColor)); 1000 secondaryLayout.addView(textView); 1001 1002 if (mSettings.isWatermarkEnabled(true /* defaultValue */)) { 1003 final ImageView imageView = new ImageView(getContext()); 1004 final Bitmap bitmap = Watermark.create(); 1005 imageView.setImageBitmap(bitmap); 1006 secondaryLayout.addView(imageView, 1007 new FrameLayout.LayoutParams(bitmap.getWidth(), bitmap.getHeight(), 1008 mSettings.getWatermarkGravity(Gravity.CENTER))); 1009 } 1010 1011 mLayout.addView(secondaryLayout); 1012 } 1013 1014 addView(mLayout, MATCH_PARENT, mainSpacerHeight); 1015 1016 final int systemUiVisibility = mSettings.getInputViewSystemUiVisibility(0); 1017 if (systemUiVisibility != 0) { 1018 setSystemUiVisibility(systemUiVisibility); 1019 } 1020 1021 if (mSettings.getDrawsBehindNavBar()) { 1022 mDrawsBehindNavBar = true; 1023 mMockIme.getWindow().getWindow().setDecorFitsSystemWindows(false); 1024 setSystemUiVisibility(getSystemUiVisibility() 1025 | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 1026 } 1027 1028 mLayoutListener = (View v, int left, int top, int right, int bottom, int oldLeft, 1029 int oldTop, int oldRight, int oldBottom) -> 1030 onInputViewLayoutChangedCallback.accept( 1031 ImeLayoutInfo.fromLayoutListenerCallback( 1032 v, left, top, right, bottom, oldLeft, oldTop, oldRight, 1033 oldBottom)); 1034 this.addOnLayoutChangeListener(mLayoutListener); 1035 } 1036 setHeight(int height)1037 private void setHeight(int height) { 1038 mLayout.getLayoutParams().height = height; 1039 mLayout.requestLayout(); 1040 } 1041 updateBottomPaddingIfNecessary(int newPaddingBottom)1042 private void updateBottomPaddingIfNecessary(int newPaddingBottom) { 1043 if (getPaddingBottom() != newPaddingBottom) { 1044 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), newPaddingBottom); 1045 1046 // TODO (b/381512167): Remove this when b/381512167 is fixed. 1047 clearMeasureCacheOfAncestors(getParent()); 1048 } 1049 } 1050 clearMeasureCacheOfAncestors(ViewParent parent)1051 private void clearMeasureCacheOfAncestors(ViewParent parent) { 1052 while (parent instanceof View view) { 1053 view.forceLayout(); 1054 parent = view.getParent(); 1055 } 1056 } 1057 1058 @Override onApplyWindowInsets(WindowInsets insets)1059 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1060 if (insets.isConsumed() 1061 || mDrawsBehindNavBar 1062 || (getSystemUiVisibility() & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) { 1063 // In this case we are not interested in consuming NavBar region. 1064 // Make sure that the bottom padding is empty. 1065 updateBottomPaddingIfNecessary(0); 1066 return insets; 1067 } 1068 1069 final int possibleNavBarHeight = insets.getSystemWindowInsetBottom(); 1070 updateBottomPaddingIfNecessary(possibleNavBarHeight); 1071 return possibleNavBarHeight <= 0 1072 ? insets 1073 : insets.replaceSystemWindowInsets( 1074 insets.getSystemWindowInsetLeft(), 1075 insets.getSystemWindowInsetTop(), 1076 insets.getSystemWindowInsetRight(), 1077 0 /* bottom */); 1078 } 1079 1080 @Override onWindowVisibilityChanged(int visibility)1081 protected void onWindowVisibilityChanged(int visibility) { 1082 mMockIme.getTracer().onWindowVisibilityChanged(() -> { 1083 super.onWindowVisibilityChanged(visibility); 1084 }, visibility); 1085 } 1086 1087 @Override onDetachedFromWindow()1088 protected void onDetachedFromWindow() { 1089 super.onDetachedFromWindow(); 1090 removeOnLayoutChangeListener(mLayoutListener); 1091 } 1092 1093 @MainThread updateInlineSuggestions( @onNull PendingInlineSuggestions pendingInlineSuggestions)1094 private void updateInlineSuggestions( 1095 @NonNull PendingInlineSuggestions pendingInlineSuggestions) { 1096 Log.d(TAG, "updateInlineSuggestions() called: " + pendingInlineSuggestions.mTotalCount); 1097 if (mSuggestionView == null || !pendingInlineSuggestions.mValid.get()) { 1098 return; 1099 } 1100 mSuggestionView.removeAllViews(); 1101 for (int i = 0; i < pendingInlineSuggestions.mTotalCount; i++) { 1102 View view = pendingInlineSuggestions.mViews[i]; 1103 if (view == null) { 1104 continue; 1105 } 1106 mSuggestionView.addView(view); 1107 } 1108 } 1109 } 1110 1111 KeyboardLayoutView mView; 1112 onInputViewLayoutChanged(@onNull ImeLayoutInfo layoutInfo)1113 private void onInputViewLayoutChanged(@NonNull ImeLayoutInfo layoutInfo) { 1114 getTracer().onInputViewLayoutChanged(layoutInfo, () -> { }); 1115 } 1116 1117 @Override onCreateInputView()1118 public View onCreateInputView() { 1119 return getTracer().onCreateInputView(() -> { 1120 mView = new KeyboardLayoutView(this, mSettings, this::onInputViewLayoutChanged); 1121 return mView; 1122 }); 1123 } 1124 1125 @Override 1126 public void onStartInput(EditorInfo editorInfo, boolean restarting) { 1127 getTracer().onStartInput(editorInfo, restarting, 1128 () -> super.onStartInput(editorInfo, restarting)); 1129 } 1130 1131 @Override 1132 public void onStartInputView(EditorInfo editorInfo, boolean restarting) { 1133 getTracer().onStartInputView(editorInfo, restarting, 1134 () -> super.onStartInputView(editorInfo, restarting)); 1135 } 1136 1137 @Override 1138 public void onPrepareStylusHandwriting() { 1139 getTracer().onPrepareStylusHandwriting(() -> super.onPrepareStylusHandwriting()); 1140 } 1141 1142 @Override 1143 public boolean onStartStylusHandwriting() { 1144 if (mEvents != null) { 1145 mEvents.clear(); 1146 } 1147 getTracer().onStartStylusHandwriting(() -> super.onStartStylusHandwriting()); 1148 return true; 1149 } 1150 1151 @Override 1152 public boolean onStartConnectionlessStylusHandwriting( 1153 int inputType, @Nullable CursorAnchorInfo cursorAnchorInfo) { 1154 if (mEvents != null) { 1155 mEvents.clear(); 1156 } 1157 getTracer().onStartConnectionlessStylusHandwriting( 1158 () -> super.onStartConnectionlessStylusHandwriting(inputType, cursorAnchorInfo)); 1159 return mSettings.isConnectionlessHandwritingEnabled(); 1160 } 1161 1162 @Override 1163 public void onStylusHandwritingMotionEvent(@NonNull MotionEvent motionEvent) { 1164 if (mEvents == null) { 1165 mEvents = new ArrayList<>(); 1166 } 1167 mEvents.add(MotionEvent.obtain(motionEvent)); 1168 getTracer().onStylusHandwritingMotionEvent(() 1169 -> super.onStylusHandwritingMotionEvent(motionEvent)); 1170 } 1171 1172 @Override 1173 public void onUpdateEditorToolType(int toolType) { 1174 if (mEvents != null) { 1175 mEvents.clear(); 1176 } 1177 getTracer().onUpdateEditorToolType(toolType, () -> super.onUpdateEditorToolType(toolType)); 1178 } 1179 1180 @Override 1181 public void onFinishStylusHandwriting() { 1182 getTracer().onFinishStylusHandwriting(() -> super.onFinishStylusHandwriting()); 1183 } 1184 1185 1186 @Override 1187 public void onFinishInputView(boolean finishingInput) { 1188 getTracer().onFinishInputView(finishingInput, 1189 () -> super.onFinishInputView(finishingInput)); 1190 } 1191 1192 @Override 1193 public void onFinishInput() { 1194 getTracer().onFinishInput(() -> super.onFinishInput()); 1195 } 1196 1197 @Override 1198 public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent keyEvent) { 1199 return getTracer().onShouldVerifyKeyEvent(keyEvent, () -> Flags.verifyKeyEvent()); 1200 } 1201 1202 @Override 1203 public boolean onKeyDown(int keyCode, KeyEvent event) { 1204 if (!Looper.getMainLooper().isCurrentThread()) { 1205 throw new IllegalStateException("onKeyDown must be called on the UI thread"); 1206 } 1207 return getTracer().onKeyDown(keyCode, event, () -> super.onKeyDown(keyCode, event)); 1208 } 1209 1210 @Override 1211 public boolean onKeyUp(int keyCode, KeyEvent event) { 1212 if (!Looper.getMainLooper().isCurrentThread()) { 1213 throw new IllegalStateException("onKeyUp must be called on the UI thread"); 1214 } 1215 return getTracer().onKeyUp(keyCode, event, () -> super.onKeyUp(keyCode, event)); 1216 } 1217 1218 @Override 1219 public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { 1220 getTracer().onUpdateCursorAnchorInfo(cursorAnchorInfo, 1221 () -> super.onUpdateCursorAnchorInfo(cursorAnchorInfo)); 1222 } 1223 1224 @Override 1225 public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, 1226 int candidatesStart, int candidatesEnd) { 1227 getTracer().onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 1228 candidatesStart, candidatesEnd, 1229 () -> super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 1230 candidatesStart, candidatesEnd)); 1231 } 1232 1233 @CallSuper 1234 public boolean onEvaluateInputViewShown() { 1235 return getTracer().onEvaluateInputViewShown(() -> { 1236 // onShowInputRequested() is indeed @CallSuper so we always call this, even when the 1237 // result is ignored. 1238 final boolean originalResult = super.onEvaluateInputViewShown(); 1239 if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) { 1240 final Configuration config = getResources().getConfiguration(); 1241 if (config.keyboard != Configuration.KEYBOARD_NOKEYS 1242 && config.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES) { 1243 // Override the behavior of InputMethodService#onEvaluateInputViewShown() 1244 return true; 1245 } 1246 } 1247 return originalResult; 1248 }); 1249 } 1250 1251 @Override 1252 public boolean onShowInputRequested(int flags, boolean configChange) { 1253 return getTracer().onShowInputRequested(flags, configChange, () -> { 1254 // onShowInputRequested() is not marked with @CallSuper, but just in case. 1255 final boolean originalResult = super.onShowInputRequested(flags, configChange); 1256 if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) { 1257 if ((flags & InputMethod.SHOW_EXPLICIT) == 0 1258 && getResources().getConfiguration().keyboard 1259 != Configuration.KEYBOARD_NOKEYS) { 1260 // Override the behavior of InputMethodService#onShowInputRequested() 1261 return true; 1262 } 1263 } 1264 return originalResult; 1265 }); 1266 } 1267 1268 @Override 1269 public void onDestroy() { 1270 getTracer().onDestroy(() -> { 1271 mDestroying = true; 1272 super.onDestroy(); 1273 if (mSettings.isWindowLayoutInfoCallbackEnabled() && mWindowLayoutComponent != null) { 1274 mWindowLayoutComponent.removeWindowLayoutInfoListener(mWindowLayoutInfoConsumer); 1275 } 1276 mSettings.getChannel().unregisterListener(mCommandHandler); 1277 mHandlerThread.quitSafely(); 1278 }); 1279 } 1280 1281 @Override 1282 public AbstractInputMethodImpl onCreateInputMethodInterface() { 1283 return getTracer().onCreateInputMethodInterface(() -> new MockInputMethodImpl()); 1284 } 1285 1286 private final ThreadLocal<Tracer> mThreadLocalTracer = new ThreadLocal<>(); 1287 1288 private Tracer getTracer() { 1289 Tracer tracer = mThreadLocalTracer.get(); 1290 if (tracer == null) { 1291 tracer = new Tracer(this); 1292 mThreadLocalTracer.set(tracer); 1293 } 1294 return tracer; 1295 } 1296 1297 @NonNull 1298 private ImeState getState() { 1299 final boolean hasInputBinding = getCurrentInputBinding() != null; 1300 final boolean hasFallbackInputConnection = 1301 !hasInputBinding 1302 || getCurrentInputConnection() == getCurrentInputBinding().getConnection(); 1303 return new ImeState(hasInputBinding, hasFallbackInputConnection); 1304 } 1305 1306 private PendingInlineSuggestions mPendingInlineSuggestions; 1307 1308 private static final class PendingInlineSuggestions { 1309 final InlineSuggestionsResponse mResponse; 1310 final int mTotalCount; 1311 final View[] mViews; 1312 final AtomicInteger mInflatedViewCount; 1313 final AtomicBoolean mValid = new AtomicBoolean(true); 1314 1315 PendingInlineSuggestions(InlineSuggestionsResponse response) { 1316 mResponse = response; 1317 mTotalCount = response.getInlineSuggestions().size(); 1318 mViews = new View[mTotalCount]; 1319 mInflatedViewCount = new AtomicInteger(0); 1320 } 1321 } 1322 1323 @MainThread 1324 @Override 1325 public InlineSuggestionsRequest onCreateInlineSuggestionsRequest(Bundle uiExtras) { 1326 StylesBuilder stylesBuilder = UiVersions.newStylesBuilder(); 1327 stylesBuilder.addStyle(InlineSuggestionUi.newStyleBuilder().build()); 1328 Bundle styles = stylesBuilder.build(); 1329 1330 final boolean supportedInlineSuggestions; 1331 Bundle inlineSuggestionsExtras = SettingsProvider.getInlineSuggestionsExtras(); 1332 if (inlineSuggestionsExtras != null) { 1333 styles.putAll(inlineSuggestionsExtras); 1334 supportedInlineSuggestions = 1335 inlineSuggestionsExtras.getBoolean("InlineSuggestions", true); 1336 } else { 1337 supportedInlineSuggestions = true; 1338 } 1339 1340 if (!supportedInlineSuggestions) { 1341 return null; 1342 } 1343 1344 return getTracer().onCreateInlineSuggestionsRequest(() -> { 1345 final ArrayList<InlinePresentationSpec> presentationSpecs = new ArrayList<>(); 1346 presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100), 1347 new Size(400, 100)).setStyle(styles).build()); 1348 presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100), 1349 new Size(400, 100)).setStyle(styles).build()); 1350 1351 final InlinePresentationSpec tooltipSpec = 1352 new InlinePresentationSpec.Builder(new Size(100, 100), 1353 new Size(400, 100)).setStyle(styles).build(); 1354 final InlineSuggestionsRequest.Builder builder = 1355 new InlineSuggestionsRequest.Builder(presentationSpecs) 1356 .setInlineTooltipPresentationSpec(tooltipSpec) 1357 .setMaxSuggestionCount(6); 1358 if (inlineSuggestionsExtras != null) { 1359 builder.setExtras(inlineSuggestionsExtras.deepCopy()); 1360 } 1361 return builder.build(); 1362 }); 1363 } 1364 1365 @MainThread 1366 @Override 1367 public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) { 1368 return getTracer().onInlineSuggestionsResponse(response, () -> { 1369 final PendingInlineSuggestions pendingInlineSuggestions = 1370 new PendingInlineSuggestions(response); 1371 if (mPendingInlineSuggestions != null) { 1372 mPendingInlineSuggestions.mValid.set(false); 1373 } 1374 mPendingInlineSuggestions = pendingInlineSuggestions; 1375 if (pendingInlineSuggestions.mTotalCount == 0) { 1376 if (mView != null) { 1377 mView.updateInlineSuggestions(pendingInlineSuggestions); 1378 } 1379 return true; 1380 } 1381 1382 final ExecutorService executorService = Executors.newCachedThreadPool(); 1383 for (int i = 0; i < pendingInlineSuggestions.mTotalCount; i++) { 1384 final int index = i; 1385 InlineSuggestion inlineSuggestion = 1386 pendingInlineSuggestions.mResponse.getInlineSuggestions().get(index); 1387 inlineSuggestion.inflate( 1388 this, 1389 new Size(WRAP_CONTENT, WRAP_CONTENT), 1390 executorService, 1391 suggestionView -> { 1392 Log.d(TAG, "new inline suggestion view ready"); 1393 if (suggestionView != null) { 1394 suggestionView.setOnClickListener((v) -> { 1395 getTracer().onInlineSuggestionClickedEvent(() -> { }); 1396 }); 1397 suggestionView.setOnLongClickListener((v) -> { 1398 getTracer().onInlineSuggestionLongClickedEvent(() -> { }); 1399 return true; 1400 }); 1401 pendingInlineSuggestions.mViews[index] = suggestionView; 1402 } 1403 if (pendingInlineSuggestions.mInflatedViewCount.incrementAndGet() 1404 == pendingInlineSuggestions.mTotalCount 1405 && pendingInlineSuggestions.mValid.get()) { 1406 Log.d(TAG, "ready to display all suggestions"); 1407 mMainHandler.post(() -> 1408 mView.updateInlineSuggestions(pendingInlineSuggestions)); 1409 } 1410 }); 1411 } 1412 return true; 1413 }); 1414 } 1415 1416 @Override 1417 public void onConfigurationChanged(Configuration configuration) { 1418 // Broadcasting configuration change is implemented at WindowProviderService level. 1419 super.onConfigurationChanged(configuration); 1420 getTracer().onConfigurationChanged(() -> {}, configuration); 1421 mLastDispatchedConfiguration.setTo(configuration); 1422 } 1423 1424 @Override 1425 public void onComputeInsets(Insets outInsets) { 1426 if (mSettings != null && mSettings.isZeroInsetsEnabled()) { 1427 final int height = getWindow().getWindow().getDecorView().getHeight(); 1428 outInsets.contentTopInsets = height; 1429 outInsets.visibleTopInsets = height; 1430 } else { 1431 super.onComputeInsets(outInsets); 1432 } 1433 } 1434 1435 @Override 1436 public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) { 1437 getTracer().onCustomImeSwitcherButtonRequestedVisible(visible, 1438 () -> super.onCustomImeSwitcherButtonRequestedVisible(visible)); 1439 } 1440 1441 /** 1442 * Event tracing helper class for {@link MockIme}. 1443 */ 1444 private static final class Tracer { 1445 1446 @NonNull 1447 private final MockIme mIme; 1448 1449 private final int mThreadId = Process.myTid(); 1450 1451 @NonNull 1452 private final String mThreadName = 1453 Thread.currentThread().getName() != null ? Thread.currentThread().getName() : ""; 1454 1455 private final boolean mIsMainThread = 1456 Looper.getMainLooper().getThread() == Thread.currentThread(); 1457 1458 private int mNestLevel = 0; 1459 1460 Tracer(@NonNull MockIme mockIme) { 1461 mIme = mockIme; 1462 } 1463 1464 private void sendEventInternal(@NonNull ImeEvent event) { 1465 if (mIme.mSettings == null) { 1466 Log.e(TAG, "Tracer cannot be used before onCreate()"); 1467 return; 1468 } 1469 if (!mIme.mSettings.getChannel().send(event.toBundle())) { 1470 Log.w(TAG, "Channel already closed: " + event.getEventName(), new Throwable()); 1471 } 1472 } 1473 1474 private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable) { 1475 recordEventInternal(eventName, runnable, new Bundle()); 1476 } 1477 1478 private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable, 1479 @NonNull Bundle arguments) { 1480 recordEventInternal(eventName, () -> { 1481 runnable.run(); return ImeEvent.RETURN_VALUE_UNAVAILABLE; 1482 }, arguments); 1483 } 1484 1485 private <T> T recordEventInternal(@NonNull String eventName, 1486 @NonNull Supplier<T> supplier) { 1487 return recordEventInternal(eventName, supplier, new Bundle()); 1488 } 1489 1490 private <T> T recordEventInternal(@NonNull String eventName, 1491 @NonNull Supplier<T> supplier, @NonNull Bundle arguments) { 1492 final ImeState enterState = mIme.getState(); 1493 final long enterTimestamp = SystemClock.elapsedRealtimeNanos(); 1494 final long enterWallTime = System.currentTimeMillis(); 1495 final int nestLevel = mNestLevel; 1496 // Send enter event 1497 sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName, 1498 mThreadId, mIsMainThread, enterTimestamp, 0, enterWallTime, 1499 0, enterState, null, arguments, 1500 ImeEvent.RETURN_VALUE_UNAVAILABLE)); 1501 ++mNestLevel; 1502 T result; 1503 try { 1504 result = supplier.get(); 1505 } finally { 1506 --mNestLevel; 1507 } 1508 final long exitTimestamp = SystemClock.elapsedRealtimeNanos(); 1509 final long exitWallTime = System.currentTimeMillis(); 1510 final ImeState exitState = mIme.getState(); 1511 // Send exit event 1512 sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName, 1513 mThreadId, mIsMainThread, enterTimestamp, exitTimestamp, enterWallTime, 1514 exitWallTime, enterState, exitState, arguments, result)); 1515 return result; 1516 } 1517 1518 void onCreate(@NonNull Runnable runnable) { 1519 recordEventInternal("onCreate", runnable); 1520 } 1521 1522 void createSession(@NonNull Runnable runnable) { 1523 recordEventInternal("createSession", runnable); 1524 } 1525 1526 void onVerify(String name, @NonNull BooleanSupplier supplier) { 1527 final Bundle arguments = new Bundle(); 1528 arguments.putString("name", name); 1529 recordEventInternal("onVerify", supplier::getAsBoolean, arguments); 1530 } 1531 1532 void onCurrentInputMethodSubtypeChanged(InputMethodSubtype newSubtype, 1533 @NonNull Runnable runnable) { 1534 final Bundle arguments = new Bundle(); 1535 arguments.putParcelable("newSubtype", newSubtype); 1536 recordEventInternal("onCurrentInputMethodSubtypeChanged", runnable, arguments); 1537 } 1538 1539 void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly, 1540 @NonNull Runnable runnable) { 1541 final Bundle arguments = new Bundle(); 1542 arguments.putBoolean("isFullscreen", isFullscreen); 1543 arguments.putBoolean("isCandidatesOnly", isCandidatesOnly); 1544 recordEventInternal("onConfigureWindow", runnable, arguments); 1545 } 1546 1547 boolean onEvaluateFullscreenMode(@NonNull BooleanSupplier supplier) { 1548 return recordEventInternal("onEvaluateFullscreenMode", supplier::getAsBoolean); 1549 } 1550 1551 boolean onEvaluateInputViewShown(@NonNull BooleanSupplier supplier) { 1552 return recordEventInternal("onEvaluateInputViewShown", supplier::getAsBoolean); 1553 } 1554 1555 View onCreateInputView(@NonNull Supplier<View> supplier) { 1556 return recordEventInternal("onCreateInputView", supplier); 1557 } 1558 1559 void onStartInput(EditorInfo editorInfo, boolean restarting, @NonNull Runnable runnable) { 1560 final Bundle arguments = new Bundle(); 1561 arguments.putParcelable("editorInfo", editorInfo); 1562 arguments.putBoolean("restarting", restarting); 1563 recordEventInternal("onStartInput", runnable, arguments); 1564 } 1565 1566 void onWindowVisibilityChanged(@NonNull Runnable runnable, int visibility) { 1567 final Bundle arguments = new Bundle(); 1568 arguments.putInt("visible", visibility); 1569 recordEventInternal("onWindowVisibilityChanged", runnable, arguments); 1570 } 1571 1572 void onStartInputView(EditorInfo editorInfo, boolean restarting, 1573 @NonNull Runnable runnable) { 1574 final Bundle arguments = new Bundle(); 1575 arguments.putParcelable("editorInfo", editorInfo); 1576 arguments.putBoolean("restarting", restarting); 1577 recordEventInternal("onStartInputView", runnable, arguments); 1578 } 1579 1580 void onPrepareStylusHandwriting(@NonNull Runnable runnable) { 1581 final Bundle arguments = new Bundle(); 1582 arguments.putParcelable("editorInfo", mIme.getCurrentInputEditorInfo()); 1583 recordEventInternal("onPrepareStylusHandwriting", runnable, arguments); 1584 } 1585 1586 void onStartStylusHandwriting(@NonNull Runnable runnable) { 1587 final Bundle arguments = new Bundle(); 1588 arguments.putParcelable("editorInfo", mIme.getCurrentInputEditorInfo()); 1589 recordEventInternal("onStartStylusHandwriting", runnable, arguments); 1590 } 1591 1592 void onStartConnectionlessStylusHandwriting(@NonNull Runnable runnable) { 1593 recordEventInternal("onStartConnectionlessStylusHandwriting", runnable); 1594 } 1595 1596 void onStylusHandwritingMotionEvent(@NonNull Runnable runnable) { 1597 recordEventInternal("onStylusMotionEvent", runnable); 1598 } 1599 1600 void onFinishStylusHandwriting(@NonNull Runnable runnable) { 1601 final Bundle arguments = new Bundle(); 1602 arguments.putParcelable("editorInfo", mIme.getCurrentInputEditorInfo()); 1603 recordEventInternal("onFinishStylusHandwriting", runnable, arguments); 1604 } 1605 1606 void onFinishInputView(boolean finishingInput, @NonNull Runnable runnable) { 1607 final Bundle arguments = new Bundle(); 1608 arguments.putBoolean("finishingInput", finishingInput); 1609 recordEventInternal("onFinishInputView", runnable, arguments); 1610 } 1611 1612 void onFinishInput(@NonNull Runnable runnable) { 1613 recordEventInternal("onFinishInput", runnable); 1614 } 1615 1616 void onUpdateEditorToolType(int toolType, @NonNull Runnable runnable) { 1617 final Bundle arguments = new Bundle(); 1618 arguments.putInt("toolType", toolType); 1619 recordEventInternal("onUpdateEditorToolType", runnable, arguments); 1620 } 1621 1622 boolean onShouldVerifyKeyEvent( 1623 @NonNull KeyEvent keyEvent, @NonNull BooleanSupplier supplier) { 1624 final Bundle arguments = new Bundle(); 1625 arguments.putParcelable("keyEvent", keyEvent); 1626 return recordEventInternal("onShouldVerifyKeyEvent", 1627 supplier::getAsBoolean, arguments); 1628 } 1629 1630 boolean onKeyDown(int keyCode, KeyEvent event, @NonNull BooleanSupplier supplier) { 1631 final Bundle arguments = new Bundle(); 1632 arguments.putInt("keyCode", keyCode); 1633 arguments.putParcelable("event", event); 1634 return recordEventInternal("onKeyDown", supplier::getAsBoolean, arguments); 1635 } 1636 1637 boolean onKeyUp(int keyCode, KeyEvent event, @NonNull BooleanSupplier supplier) { 1638 final Bundle arguments = new Bundle(); 1639 arguments.putInt("keyCode", keyCode); 1640 arguments.putParcelable("event", event); 1641 return recordEventInternal("onKeyUp", supplier::getAsBoolean, arguments); 1642 } 1643 1644 void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo, 1645 @NonNull Runnable runnable) { 1646 final Bundle arguments = new Bundle(); 1647 arguments.putParcelable("cursorAnchorInfo", cursorAnchorInfo); 1648 recordEventInternal("onUpdateCursorAnchorInfo", runnable, arguments); 1649 } 1650 1651 void onUpdateSelection(int oldSelStart, 1652 int oldSelEnd, 1653 int newSelStart, 1654 int newSelEnd, 1655 int candidatesStart, 1656 int candidatesEnd, 1657 @NonNull Runnable runnable) { 1658 final Bundle arguments = new Bundle(); 1659 arguments.putInt("oldSelStart", oldSelStart); 1660 arguments.putInt("oldSelEnd", oldSelEnd); 1661 arguments.putInt("newSelStart", newSelStart); 1662 arguments.putInt("newSelEnd", newSelEnd); 1663 arguments.putInt("candidatesStart", candidatesStart); 1664 arguments.putInt("candidatesEnd", candidatesEnd); 1665 recordEventInternal("onUpdateSelection", runnable, arguments); 1666 } 1667 1668 boolean onShowInputRequested(int flags, boolean configChange, 1669 @NonNull BooleanSupplier supplier) { 1670 final Bundle arguments = new Bundle(); 1671 arguments.putInt("flags", flags); 1672 arguments.putBoolean("configChange", configChange); 1673 return recordEventInternal("onShowInputRequested", supplier::getAsBoolean, arguments); 1674 } 1675 1676 void onDestroy(@NonNull Runnable runnable) { 1677 recordEventInternal("onDestroy", runnable); 1678 } 1679 1680 void attachToken(IBinder token, @NonNull Runnable runnable) { 1681 final Bundle arguments = new Bundle(); 1682 arguments.putBinder("token", token); 1683 recordEventInternal("attachToken", runnable, arguments); 1684 } 1685 1686 void bindInput(InputBinding binding, @NonNull Runnable runnable) { 1687 final Bundle arguments = new Bundle(); 1688 arguments.putParcelable("binding", binding); 1689 recordEventInternal("bindInput", runnable, arguments); 1690 } 1691 1692 void unbindInput(@NonNull Runnable runnable) { 1693 recordEventInternal("unbindInput", runnable); 1694 } 1695 1696 void showSoftInput(int flags, ResultReceiver resultReceiver, @NonNull Runnable runnable) { 1697 final Bundle arguments = new Bundle(); 1698 arguments.putInt("flags", flags); 1699 arguments.putParcelable("resultReceiver", resultReceiver); 1700 recordEventInternal("showSoftInput", runnable, arguments); 1701 } 1702 1703 void hideSoftInput(int flags, ResultReceiver resultReceiver, @NonNull Runnable runnable) { 1704 final Bundle arguments = new Bundle(); 1705 arguments.putInt("flags", flags); 1706 arguments.putParcelable("resultReceiver", resultReceiver); 1707 recordEventInternal("hideSoftInput", runnable, arguments); 1708 } 1709 1710 AbstractInputMethodImpl onCreateInputMethodInterface( 1711 @NonNull Supplier<AbstractInputMethodImpl> supplier) { 1712 return recordEventInternal("onCreateInputMethodInterface", supplier); 1713 } 1714 1715 void onReceiveCommand(@NonNull ImeCommand command, @NonNull Runnable runnable) { 1716 final Bundle arguments = new Bundle(); 1717 arguments.putBundle("command", command.toBundle()); 1718 recordEventInternal("onReceiveCommand", runnable, arguments); 1719 } 1720 1721 void onHandleCommand( 1722 @NonNull ImeCommand command, @NonNull Supplier<Object> resultSupplier) { 1723 final Bundle arguments = new Bundle(); 1724 arguments.putBundle("command", command.toBundle()); 1725 recordEventInternal("onHandleCommand", resultSupplier, arguments); 1726 } 1727 1728 void onInputViewLayoutChanged(@NonNull ImeLayoutInfo imeLayoutInfo, 1729 @NonNull Runnable runnable) { 1730 final Bundle arguments = new Bundle(); 1731 imeLayoutInfo.writeToBundle(arguments); 1732 recordEventInternal("onInputViewLayoutChanged", runnable, arguments); 1733 } 1734 1735 void onStrictModeViolated(@NonNull Runnable runnable) { 1736 final Bundle arguments = new Bundle(); 1737 recordEventInternal("onStrictModeViolated", runnable, arguments); 1738 } 1739 1740 InlineSuggestionsRequest onCreateInlineSuggestionsRequest( 1741 @NonNull Supplier<InlineSuggestionsRequest> supplier) { 1742 return recordEventInternal("onCreateInlineSuggestionsRequest", supplier); 1743 } 1744 1745 boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response, 1746 @NonNull BooleanSupplier supplier) { 1747 final Bundle arguments = new Bundle(); 1748 arguments.putParcelable("response", response); 1749 return recordEventInternal("onInlineSuggestionsResponse", supplier::getAsBoolean, 1750 arguments); 1751 } 1752 1753 void onInlineSuggestionClickedEvent(@NonNull Runnable runnable) { 1754 final Bundle arguments = new Bundle(); 1755 recordEventInternal("onInlineSuggestionClickedEvent", runnable, arguments); 1756 } 1757 1758 void onInlineSuggestionLongClickedEvent(@NonNull Runnable runnable) { 1759 final Bundle arguments = new Bundle(); 1760 recordEventInternal("onInlineSuggestionLongClickedEvent", runnable, arguments); 1761 } 1762 1763 void onConfigurationChanged(@NonNull Runnable runnable, Configuration configuration) { 1764 final Bundle arguments = new Bundle(); 1765 arguments.putParcelable("Configuration", configuration); 1766 arguments.putInt("ConfigUpdates", configuration.diff( 1767 mIme.mLastDispatchedConfiguration)); 1768 recordEventInternal("onConfigurationChanged", runnable, arguments); 1769 } 1770 1771 void onPerformHandwritingGestureResult(int result, long requestId, Runnable runnable) { 1772 final Bundle arguments = new Bundle(); 1773 arguments.putInt("result", result); 1774 arguments.putLong("requestId", requestId); 1775 recordEventInternal("onPerformHandwritingGestureResult", runnable, arguments); 1776 } 1777 1778 public void onRequestTextBoundsInfoResult(TextBoundsInfoResult result, long requestId) { 1779 final Bundle arguments = new Bundle(); 1780 arguments.putInt("resultCode", result.getResultCode()); 1781 arguments.putParcelable("boundsInfo", result.getTextBoundsInfo()); 1782 arguments.putLong("requestId", requestId); 1783 recordEventInternal("onRequestTextBoundsInfoResult", () -> {}, arguments); 1784 } 1785 1786 void getWindowLayoutInfo(@NonNull WindowLayoutInfo windowLayoutInfo, 1787 @NonNull Runnable runnable) { 1788 final Bundle arguments = new Bundle(); 1789 ImeEventStreamTestUtils.WindowLayoutInfoParcelable parcel = 1790 new ImeEventStreamTestUtils.WindowLayoutInfoParcelable(windowLayoutInfo); 1791 arguments.putParcelable("WindowLayoutInfo", parcel); 1792 recordEventInternal("getWindowLayoutInfo", runnable, arguments); 1793 } 1794 1795 void onCustomImeSwitcherButtonRequestedVisible(boolean visible, 1796 @NonNull Runnable runnable) { 1797 final Bundle arguments = new Bundle(); 1798 arguments.putBoolean("visible", visible); 1799 recordEventInternal("onCustomImeSwitcherButtonRequestedVisible", runnable, arguments); 1800 } 1801 } 1802 } 1803