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.view.ViewGroup.LayoutParams.MATCH_PARENT;
20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
21 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
22 
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.PackageManager;
29 import android.content.res.Configuration;
30 import android.graphics.Bitmap;
31 import android.inputmethodservice.InputMethodService;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.Process;
39 import android.os.ResultReceiver;
40 import android.os.StrictMode;
41 import android.os.SystemClock;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.Size;
45 import android.util.TypedValue;
46 import android.view.Display;
47 import android.view.GestureDetector;
48 import android.view.Gravity;
49 import android.view.KeyEvent;
50 import android.view.View;
51 import android.view.ViewConfiguration;
52 import android.view.Window;
53 import android.view.WindowInsets;
54 import android.view.WindowManager;
55 import android.view.inputmethod.CompletionInfo;
56 import android.view.inputmethod.CorrectionInfo;
57 import android.view.inputmethod.CursorAnchorInfo;
58 import android.view.inputmethod.EditorInfo;
59 import android.view.inputmethod.ExtractedTextRequest;
60 import android.view.inputmethod.InlineSuggestion;
61 import android.view.inputmethod.InlineSuggestionsRequest;
62 import android.view.inputmethod.InlineSuggestionsResponse;
63 import android.view.inputmethod.InputBinding;
64 import android.view.inputmethod.InputConnection;
65 import android.view.inputmethod.InputContentInfo;
66 import android.view.inputmethod.InputMethod;
67 import android.widget.FrameLayout;
68 import android.widget.HorizontalScrollView;
69 import android.widget.ImageView;
70 import android.widget.LinearLayout;
71 import android.widget.TextView;
72 import android.widget.inline.InlinePresentationSpec;
73 
74 import androidx.annotation.AnyThread;
75 import androidx.annotation.CallSuper;
76 import androidx.annotation.MainThread;
77 import androidx.annotation.NonNull;
78 import androidx.annotation.Nullable;
79 import androidx.annotation.WorkerThread;
80 import androidx.autofill.inline.UiVersions;
81 import androidx.autofill.inline.UiVersions.StylesBuilder;
82 import androidx.autofill.inline.v1.InlineSuggestionUi;
83 
84 import java.util.ArrayList;
85 import java.util.concurrent.ExecutorService;
86 import java.util.concurrent.Executors;
87 import java.util.concurrent.atomic.AtomicBoolean;
88 import java.util.concurrent.atomic.AtomicInteger;
89 import java.util.concurrent.atomic.AtomicReference;
90 import java.util.function.BooleanSupplier;
91 import java.util.function.Consumer;
92 import java.util.function.Supplier;
93 
94 /**
95  * Mock IME for end-to-end tests.
96  */
97 public final class MockIme extends InputMethodService {
98 
99     private static final String TAG = "MockIme";
100 
101     private static final String PACKAGE_NAME = "com.android.cts.mockime";
102 
getComponentName()103     static ComponentName getComponentName() {
104         return new ComponentName(PACKAGE_NAME, MockIme.class.getName());
105     }
106 
getImeId()107     static String getImeId() {
108         return getComponentName().flattenToShortString();
109     }
110 
getCommandActionName(@onNull String eventActionName)111     static String getCommandActionName(@NonNull String eventActionName) {
112         return eventActionName + ".command";
113     }
114 
115     private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver");
116 
117     private final Handler mMainHandler = new Handler();
118 
119     private static final class CommandReceiver extends BroadcastReceiver {
120         @NonNull
121         private final String mActionName;
122         @NonNull
123         private final Consumer<ImeCommand> mOnReceiveCommand;
124 
CommandReceiver(@onNull String actionName, @NonNull Consumer<ImeCommand> onReceiveCommand)125         CommandReceiver(@NonNull String actionName,
126                 @NonNull Consumer<ImeCommand> onReceiveCommand) {
127             mActionName = actionName;
128             mOnReceiveCommand = onReceiveCommand;
129         }
130 
131         @Override
onReceive(Context context, Intent intent)132         public void onReceive(Context context, Intent intent) {
133             if (TextUtils.equals(mActionName, intent.getAction())) {
134                 mOnReceiveCommand.accept(ImeCommand.fromBundle(intent.getExtras()));
135             }
136         }
137     }
138 
139     @Nullable
140     private InputConnection mMemorizedInputConnection = null;
141 
142     @Nullable
143     @MainThread
getMemorizedOrCurrentInputConnection()144     private InputConnection getMemorizedOrCurrentInputConnection() {
145         return mMemorizedInputConnection != null
146                 ? mMemorizedInputConnection : getCurrentInputConnection();
147     }
148 
149     @WorkerThread
onReceiveCommand(@onNull ImeCommand command)150     private void onReceiveCommand(@NonNull ImeCommand command) {
151         getTracer().onReceiveCommand(command, () -> {
152             if (command.shouldDispatchToMainThread()) {
153                 mMainHandler.post(() -> onHandleCommand(command));
154             } else {
155                 onHandleCommand(command);
156             }
157         });
158     }
159 
160     @AnyThread
onHandleCommand(@onNull ImeCommand command)161     private void onHandleCommand(@NonNull ImeCommand command) {
162         getTracer().onHandleCommand(command, () -> {
163             if (command.shouldDispatchToMainThread()) {
164                 if (Looper.myLooper() != Looper.getMainLooper()) {
165                     throw new IllegalStateException("command " + command
166                             + " should be handled on the main thread");
167                 }
168                 // The context which created from InputMethodService#createXXXContext must behave
169                 // like an UI context, which can obtain a display, a window manager,
170                 // a view configuration and a gesture detector instance without strict mode
171                 // violation.
172                 final Configuration testConfig = new Configuration();
173                 testConfig.setToDefaults();
174                 final Context configContext = createConfigurationContext(testConfig);
175                 final Context attrContext = createAttributionContext(null /* attributionTag */);
176                 // UI component accesses on a display context must throw strict mode violations.
177                 final Context displayContext = createDisplayContext(getDisplay());
178                 switch (command.getName()) {
179                     case "memorizeCurrentInputConnection": {
180                         if (!Looper.getMainLooper().isCurrentThread()) {
181                             return new UnsupportedOperationException(
182                                     "memorizeCurrentInputConnection can be requested only for the"
183                                             + " main thread.");
184                         }
185                         mMemorizedInputConnection = getCurrentInputConnection();
186                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
187                     }
188                     case "unmemorizeCurrentInputConnection": {
189                         if (!Looper.getMainLooper().isCurrentThread()) {
190                             return new UnsupportedOperationException(
191                                     "unmemorizeCurrentInputConnection can be requested only for the"
192                                             + " main thread.");
193                         }
194                         mMemorizedInputConnection = null;
195                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
196                     }
197                     case "getTextBeforeCursor": {
198                         final int n = command.getExtras().getInt("n");
199                         final int flag = command.getExtras().getInt("flag");
200                         return getMemorizedOrCurrentInputConnection().getTextBeforeCursor(n, flag);
201                     }
202                     case "getTextAfterCursor": {
203                         final int n = command.getExtras().getInt("n");
204                         final int flag = command.getExtras().getInt("flag");
205                         return getMemorizedOrCurrentInputConnection().getTextAfterCursor(n, flag);
206                     }
207                     case "getSelectedText": {
208                         final int flag = command.getExtras().getInt("flag");
209                         return getMemorizedOrCurrentInputConnection().getSelectedText(flag);
210                     }
211                     case "getCursorCapsMode": {
212                         final int reqModes = command.getExtras().getInt("reqModes");
213                         return getMemorizedOrCurrentInputConnection().getCursorCapsMode(reqModes);
214                     }
215                     case "getExtractedText": {
216                         final ExtractedTextRequest request =
217                                 command.getExtras().getParcelable("request");
218                         final int flags = command.getExtras().getInt("flags");
219                         return getMemorizedOrCurrentInputConnection().getExtractedText(request,
220                                 flags);
221                     }
222                     case "deleteSurroundingText": {
223                         final int beforeLength = command.getExtras().getInt("beforeLength");
224                         final int afterLength = command.getExtras().getInt("afterLength");
225                         return getMemorizedOrCurrentInputConnection().deleteSurroundingText(
226                                 beforeLength, afterLength);
227                     }
228                     case "deleteSurroundingTextInCodePoints": {
229                         final int beforeLength = command.getExtras().getInt("beforeLength");
230                         final int afterLength = command.getExtras().getInt("afterLength");
231                         return getMemorizedOrCurrentInputConnection()
232                                 .deleteSurroundingTextInCodePoints(beforeLength, afterLength);
233                     }
234                     case "setComposingText": {
235                         final CharSequence text = command.getExtras().getCharSequence("text");
236                         final int newCursorPosition =
237                                 command.getExtras().getInt("newCursorPosition");
238                         return getMemorizedOrCurrentInputConnection().setComposingText(
239                                 text, newCursorPosition);
240                     }
241                     case "setComposingRegion": {
242                         final int start = command.getExtras().getInt("start");
243                         final int end = command.getExtras().getInt("end");
244                         return getMemorizedOrCurrentInputConnection().setComposingRegion(start,
245                                 end);
246                     }
247                     case "finishComposingText":
248                         return getMemorizedOrCurrentInputConnection().finishComposingText();
249                     case "commitText": {
250                         final CharSequence text = command.getExtras().getCharSequence("text");
251                         final int newCursorPosition =
252                                 command.getExtras().getInt("newCursorPosition");
253                         return getMemorizedOrCurrentInputConnection().commitText(text,
254                                 newCursorPosition);
255                     }
256                     case "commitCompletion": {
257                         final CompletionInfo text = command.getExtras().getParcelable("text");
258                         return getMemorizedOrCurrentInputConnection().commitCompletion(text);
259                     }
260                     case "commitCorrection": {
261                         final CorrectionInfo correctionInfo =
262                                 command.getExtras().getParcelable("correctionInfo");
263                         return getMemorizedOrCurrentInputConnection().commitCorrection(
264                                 correctionInfo);
265                     }
266                     case "setSelection": {
267                         final int start = command.getExtras().getInt("start");
268                         final int end = command.getExtras().getInt("end");
269                         return getMemorizedOrCurrentInputConnection().setSelection(start, end);
270                     }
271                     case "performEditorAction": {
272                         final int editorAction = command.getExtras().getInt("editorAction");
273                         return getMemorizedOrCurrentInputConnection().performEditorAction(
274                                 editorAction);
275                     }
276                     case "performContextMenuAction": {
277                         final int id = command.getExtras().getInt("id");
278                         return getMemorizedOrCurrentInputConnection().performContextMenuAction(id);
279                     }
280                     case "beginBatchEdit":
281                         return getMemorizedOrCurrentInputConnection().beginBatchEdit();
282                     case "endBatchEdit":
283                         return getMemorizedOrCurrentInputConnection().endBatchEdit();
284                     case "sendKeyEvent": {
285                         final KeyEvent event = command.getExtras().getParcelable("event");
286                         return getMemorizedOrCurrentInputConnection().sendKeyEvent(event);
287                     }
288                     case "clearMetaKeyStates": {
289                         final int states = command.getExtras().getInt("states");
290                         return getMemorizedOrCurrentInputConnection().clearMetaKeyStates(states);
291                     }
292                     case "reportFullscreenMode": {
293                         final boolean enabled = command.getExtras().getBoolean("enabled");
294                         return getMemorizedOrCurrentInputConnection().reportFullscreenMode(enabled);
295                     }
296                     case "performSpellCheck": {
297                         return getMemorizedOrCurrentInputConnection().performSpellCheck();
298                     }
299                     case "performPrivateCommand": {
300                         final String action = command.getExtras().getString("action");
301                         final Bundle data = command.getExtras().getBundle("data");
302                         return getMemorizedOrCurrentInputConnection().performPrivateCommand(action,
303                                 data);
304                     }
305                     case "requestCursorUpdates": {
306                         final int cursorUpdateMode = command.getExtras().getInt("cursorUpdateMode");
307                         return getMemorizedOrCurrentInputConnection().requestCursorUpdates(
308                                 cursorUpdateMode);
309                     }
310                     case "getHandler":
311                         return getMemorizedOrCurrentInputConnection().getHandler();
312                     case "closeConnection":
313                         getMemorizedOrCurrentInputConnection().closeConnection();
314                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
315                     case "commitContent": {
316                         final InputContentInfo inputContentInfo =
317                                 command.getExtras().getParcelable("inputContentInfo");
318                         final int flags = command.getExtras().getInt("flags");
319                         final Bundle opts = command.getExtras().getBundle("opts");
320                         return getMemorizedOrCurrentInputConnection().commitContent(
321                                 inputContentInfo, flags, opts);
322                     }
323                     case "setBackDisposition": {
324                         final int backDisposition =
325                                 command.getExtras().getInt("backDisposition");
326                         setBackDisposition(backDisposition);
327                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
328                     }
329                     case "requestHideSelf": {
330                         final int flags = command.getExtras().getInt("flags");
331                         requestHideSelf(flags);
332                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
333                     }
334                     case "requestShowSelf": {
335                         final int flags = command.getExtras().getInt("flags");
336                         requestShowSelf(flags);
337                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
338                     }
339                     case "sendDownUpKeyEvents": {
340                         final int keyEventCode = command.getExtras().getInt("keyEventCode");
341                         sendDownUpKeyEvents(keyEventCode);
342                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
343                     }
344                     case "getApplicationInfo": {
345                         final String packageName = command.getExtras().getString("packageName");
346                         final int flags = command.getExtras().getInt("flags");
347                         try {
348                             return getPackageManager().getApplicationInfo(packageName, flags);
349                         } catch (PackageManager.NameNotFoundException e) {
350                             return e;
351                         }
352                     }
353                     case "getDisplayId":
354                         return getDisplay().getDisplayId();
355                     case "verifyLayoutInflaterContext":
356                         return getLayoutInflater().getContext() == this;
357                     case "setHeight":
358                         final int height = command.getExtras().getInt("height");
359                         mView.setHeight(height);
360                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
361                     case "setInlineSuggestionsExtras":
362                         mInlineSuggestionsExtras = command.getExtras();
363                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
364                     case "verifyGetDisplay":
365                         try {
366                             return verifyGetDisplay();
367                         } catch (UnsupportedOperationException e) {
368                             return e;
369                         }
370                     case "verifyGetWindowManager": {
371                         final WindowManager imsWm = getSystemService(WindowManager.class);
372                         final WindowManager configContextWm =
373                                 configContext.getSystemService(WindowManager.class);
374                         final WindowManager attrContextWm =
375                                 attrContext.getSystemService(WindowManager.class);
376                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
377                     }
378                     case "verifyGetViewConfiguration": {
379                         final ViewConfiguration imsViewConfig = ViewConfiguration.get(this);
380                         final ViewConfiguration configContextViewConfig =
381                                 ViewConfiguration.get(configContext);
382                         final ViewConfiguration attrContextViewConfig =
383                                 ViewConfiguration.get(attrContext);
384                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
385                     }
386                     case "verifyGetGestureDetector": {
387                         GestureDetector.SimpleOnGestureListener listener =
388                                 new GestureDetector.SimpleOnGestureListener();
389                         final GestureDetector imsGestureDetector =
390                                 new GestureDetector(this, listener);
391                         final GestureDetector configContextGestureDetector =
392                                 new GestureDetector(configContext, listener);
393                         final GestureDetector attrGestureDetector =
394                                 new GestureDetector(attrContext, listener);
395                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
396                     }
397                     case "verifyGetWindowManagerOnDisplayContext": {
398                         // Obtaining a WindowManager on a display context must throw a strict mode
399                         // violation.
400                         final WindowManager wm = displayContext
401                                 .getSystemService(WindowManager.class);
402 
403                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
404                     }
405                     case "verifyGetViewConfigurationOnDisplayContext": {
406                         // Obtaining a ViewConfiguration on a display context must throw a strict
407                         // mode violation.
408                         final ViewConfiguration viewConfiguration =
409                                 ViewConfiguration.get(displayContext);
410 
411                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
412                     }
413                     case "verifyGetGestureDetectorOnDisplayContext": {
414                         // Obtaining a GestureDetector on a display context must throw a strict mode
415                         // violation.
416                         GestureDetector.SimpleOnGestureListener listener =
417                                 new GestureDetector.SimpleOnGestureListener();
418                         final GestureDetector gestureDetector =
419                                 new GestureDetector(displayContext, listener);
420 
421                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
422                     }
423                 }
424             }
425             return ImeEvent.RETURN_VALUE_UNAVAILABLE;
426         });
427     }
428 
verifyGetDisplay()429     private boolean verifyGetDisplay() throws UnsupportedOperationException {
430         final Display display;
431         final Display configContextDisplay;
432         final Configuration config = new Configuration();
433         config.setToDefaults();
434         final Context configContext = createConfigurationContext(config);
435         display = getDisplay();
436         configContextDisplay = configContext.getDisplay();
437         return display != null && configContextDisplay != null;
438     }
439 
440     @Nullable
441     private Bundle mInlineSuggestionsExtras;
442 
443     @Nullable
444     private CommandReceiver mCommandReceiver;
445 
446     @Nullable
447     private ImeSettings mSettings;
448 
449     private final AtomicReference<String> mImeEventActionName = new AtomicReference<>();
450 
451     @Nullable
getImeEventActionName()452     String getImeEventActionName() {
453         return mImeEventActionName.get();
454     }
455 
456     private final AtomicReference<String> mClientPackageName = new AtomicReference<>();
457 
458     @Nullable
getClientPackageName()459     String getClientPackageName() {
460         return mClientPackageName.get();
461     }
462 
463     private class MockInputMethodImpl extends InputMethodImpl {
464         @Override
showSoftInput(int flags, ResultReceiver resultReceiver)465         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
466             getTracer().showSoftInput(flags, resultReceiver,
467                     () -> super.showSoftInput(flags, resultReceiver));
468         }
469 
470         @Override
hideSoftInput(int flags, ResultReceiver resultReceiver)471         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
472             getTracer().hideSoftInput(flags, resultReceiver,
473                     () -> super.hideSoftInput(flags, resultReceiver));
474         }
475 
476         @Override
attachToken(IBinder token)477         public void attachToken(IBinder token) {
478             getTracer().attachToken(token, () -> super.attachToken(token));
479         }
480 
481         @Override
bindInput(InputBinding binding)482         public void bindInput(InputBinding binding) {
483             getTracer().bindInput(binding, () -> super.bindInput(binding));
484         }
485 
486         @Override
unbindInput()487         public void unbindInput() {
488             getTracer().unbindInput(() -> super.unbindInput());
489         }
490     }
491 
492     @Override
onCreate()493     public void onCreate() {
494         // Initialize minimum settings to send events in Tracer#onCreate().
495         mSettings = SettingsProvider.getSettings();
496         if (mSettings == null) {
497             throw new IllegalStateException("Settings file is not found. "
498                     + "Make sure MockImeSession.create() is used to launch Mock IME.");
499         }
500         mClientPackageName.set(mSettings.getClientPackageName());
501         mImeEventActionName.set(mSettings.getEventCallbackActionName());
502 
503         // TODO(b/159593676): consider to detect more violations
504         if (mSettings.isStrictModeEnabled()) {
505             StrictMode.setVmPolicy(
506                     new StrictMode.VmPolicy.Builder()
507                             .detectIncorrectContextUse()
508                             .penaltyLog()
509                             .penaltyListener(Runnable::run,
510                                     v -> getTracer().onStrictModeViolated(() -> { }))
511                             .build());
512         }
513 
514         getTracer().onCreate(() -> {
515             super.onCreate();
516             mHandlerThread.start();
517             final String actionName = getCommandActionName(mSettings.getEventCallbackActionName());
518             mCommandReceiver = new CommandReceiver(actionName, this::onReceiveCommand);
519             final IntentFilter filter = new IntentFilter(actionName);
520             final Handler handler = new Handler(mHandlerThread.getLooper());
521             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
522                 registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler,
523                         Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
524             } else {
525                 registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
526             }
527             if (mSettings.isVerifyGetDisplayOnCreate()) {
528                 getTracer().onVerify("getDisplay", this::verifyGetDisplay);
529             }
530             final int windowFlags = mSettings.getWindowFlags(0);
531             final int windowFlagsMask = mSettings.getWindowFlagsMask(0);
532             if (windowFlags != 0 || windowFlagsMask != 0) {
533                 final int prevFlags = getWindow().getWindow().getAttributes().flags;
534                 getWindow().getWindow().setFlags(windowFlags, windowFlagsMask);
535                 // For some reasons, seems that we need to post another requestLayout() when
536                 // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS bit is changed.
537                 // TODO: Investigate the reason.
538                 if ((windowFlagsMask & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
539                     final boolean hadFlag = (prevFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
540                     final boolean hasFlag = (windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
541                     if (hadFlag != hasFlag) {
542                         final View decorView = getWindow().getWindow().getDecorView();
543                         decorView.post(() -> decorView.requestLayout());
544                     }
545                 }
546             }
547 
548             // Ensuring bar contrast interferes with the tests.
549             getWindow().getWindow().setStatusBarContrastEnforced(false);
550             getWindow().getWindow().setNavigationBarContrastEnforced(false);
551 
552             if (mSettings.hasNavigationBarColor()) {
553                 getWindow().getWindow().setNavigationBarColor(mSettings.getNavigationBarColor());
554             }
555         });
556     }
557 
558     @Override
onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly)559     public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) {
560         getTracer().onConfigureWindow(win, isFullscreen, isCandidatesOnly,
561                 () -> super.onConfigureWindow(win, isFullscreen, isCandidatesOnly));
562     }
563 
564     @Override
onEvaluateFullscreenMode()565     public boolean onEvaluateFullscreenMode() {
566         return getTracer().onEvaluateFullscreenMode(() ->
567                 mSettings.fullscreenModeAllowed(false) && super.onEvaluateFullscreenMode());
568     }
569 
570     private static final class KeyboardLayoutView extends LinearLayout {
571         @NonNull
572         private final MockIme mMockIme;
573         @NonNull
574         private final ImeSettings mSettings;
575         @NonNull
576         private final View.OnLayoutChangeListener mLayoutListener;
577 
578         private final LinearLayout mLayout;
579 
580         @Nullable
581         private final LinearLayout mSuggestionView;
582 
583         private boolean mDrawsBehindNavBar = false;
584 
KeyboardLayoutView(MockIme mockIme, @NonNull ImeSettings imeSettings, @Nullable Consumer<ImeLayoutInfo> onInputViewLayoutChangedCallback)585         KeyboardLayoutView(MockIme mockIme, @NonNull ImeSettings imeSettings,
586                 @Nullable Consumer<ImeLayoutInfo> onInputViewLayoutChangedCallback) {
587             super(mockIme);
588 
589             mMockIme = mockIme;
590             mSettings = imeSettings;
591 
592             setOrientation(VERTICAL);
593 
594             final int defaultBackgroundColor =
595                     getResources().getColor(android.R.color.holo_orange_dark, null);
596 
597             final int mainSpacerHeight = mSettings.getInputViewHeight(LayoutParams.WRAP_CONTENT);
598             mLayout = new LinearLayout(getContext());
599             mLayout.setOrientation(LinearLayout.VERTICAL);
600 
601             if (mSettings.getInlineSuggestionsEnabled()) {
602                 final HorizontalScrollView scrollView = new HorizontalScrollView(getContext());
603                 final LayoutParams scrollViewParams = new LayoutParams(MATCH_PARENT, 100);
604                 scrollView.setLayoutParams(scrollViewParams);
605 
606                 final LinearLayout suggestionView = new LinearLayout(getContext());
607                 suggestionView.setBackgroundColor(0xFFEEEEEE);
608                 final String suggestionViewContentDesc =
609                         mSettings.getInlineSuggestionViewContentDesc(null /* default */);
610                 if (suggestionViewContentDesc != null) {
611                     suggestionView.setContentDescription(suggestionViewContentDesc);
612                 }
613                 scrollView.addView(suggestionView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
614                 mSuggestionView = suggestionView;
615 
616                 mLayout.addView(scrollView);
617             } else {
618                 mSuggestionView = null;
619             }
620 
621             {
622                 final FrameLayout secondaryLayout = new FrameLayout(getContext());
623                 secondaryLayout.setForegroundGravity(Gravity.CENTER);
624 
625                 final TextView textView = new TextView(getContext());
626                 textView.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
627                 textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
628                 textView.setGravity(Gravity.CENTER);
629                 textView.setText(getImeId());
630                 textView.setBackgroundColor(
631                         mSettings.getBackgroundColor(defaultBackgroundColor));
632                 secondaryLayout.addView(textView);
633 
634                 if (mSettings.isWatermarkEnabled(true /* defaultValue */)) {
635                     final ImageView imageView = new ImageView(getContext());
636                     final Bitmap bitmap = Watermark.create();
637                     imageView.setImageBitmap(bitmap);
638                     secondaryLayout.addView(imageView,
639                             new FrameLayout.LayoutParams(bitmap.getWidth(), bitmap.getHeight(),
640                                     Gravity.CENTER));
641                 }
642 
643                 mLayout.addView(secondaryLayout);
644             }
645 
646             addView(mLayout, MATCH_PARENT, mainSpacerHeight);
647 
648             final int systemUiVisibility = mSettings.getInputViewSystemUiVisibility(0);
649             if (systemUiVisibility != 0) {
650                 setSystemUiVisibility(systemUiVisibility);
651             }
652 
653             if (mSettings.getDrawsBehindNavBar()) {
654                 mDrawsBehindNavBar = true;
655                 mMockIme.getWindow().getWindow().setDecorFitsSystemWindows(false);
656                 setSystemUiVisibility(getSystemUiVisibility()
657                         | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
658             }
659 
660             mLayoutListener = (View v, int left, int top, int right, int bottom, int oldLeft,
661                     int oldTop, int oldRight, int oldBottom) ->
662                     onInputViewLayoutChangedCallback.accept(
663                             ImeLayoutInfo.fromLayoutListenerCallback(
664                                     v, left, top, right, bottom, oldLeft, oldTop, oldRight,
665                                     oldBottom));
666             this.addOnLayoutChangeListener(mLayoutListener);
667         }
668 
setHeight(int height)669         private void setHeight(int height) {
670             mLayout.getLayoutParams().height = height;
671             mLayout.requestLayout();
672         }
673 
updateBottomPaddingIfNecessary(int newPaddingBottom)674         private void updateBottomPaddingIfNecessary(int newPaddingBottom) {
675             if (getPaddingBottom() != newPaddingBottom) {
676                 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), newPaddingBottom);
677             }
678         }
679 
680         @Override
onApplyWindowInsets(WindowInsets insets)681         public WindowInsets onApplyWindowInsets(WindowInsets insets) {
682             if (insets.isConsumed()
683                     || mDrawsBehindNavBar
684                     || (getSystemUiVisibility() & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0) {
685                 // In this case we are not interested in consuming NavBar region.
686                 // Make sure that the bottom padding is empty.
687                 updateBottomPaddingIfNecessary(0);
688                 return insets;
689             }
690 
691             // In some cases the bottom system window inset is not a navigation bar. Wear devices
692             // that have bottom chin are examples.  For now, assume that it's a navigation bar if it
693             // has the same height as the root window's stable bottom inset.
694             final WindowInsets rootWindowInsets = getRootWindowInsets();
695             if (rootWindowInsets != null && (rootWindowInsets.getStableInsetBottom()
696                     != insets.getSystemWindowInsetBottom())) {
697                 // This is probably not a NavBar.
698                 updateBottomPaddingIfNecessary(0);
699                 return insets;
700             }
701 
702             final int possibleNavBarHeight = insets.getSystemWindowInsetBottom();
703             updateBottomPaddingIfNecessary(possibleNavBarHeight);
704             return possibleNavBarHeight <= 0
705                     ? insets
706                     : insets.replaceSystemWindowInsets(
707                             insets.getSystemWindowInsetLeft(),
708                             insets.getSystemWindowInsetTop(),
709                             insets.getSystemWindowInsetRight(),
710                             0 /* bottom */);
711         }
712 
713         @Override
onWindowVisibilityChanged(int visibility)714         protected void onWindowVisibilityChanged(int visibility) {
715             mMockIme.getTracer().onWindowVisibilityChanged(() -> {
716                 super.onWindowVisibilityChanged(visibility);
717             }, visibility);
718         }
719 
720         @Override
onDetachedFromWindow()721         protected void onDetachedFromWindow() {
722             super.onDetachedFromWindow();
723             removeOnLayoutChangeListener(mLayoutListener);
724         }
725 
726         @MainThread
updateInlineSuggestions( @onNull PendingInlineSuggestions pendingInlineSuggestions)727         private void updateInlineSuggestions(
728                 @NonNull PendingInlineSuggestions pendingInlineSuggestions) {
729             Log.d(TAG, "updateInlineSuggestions() called: " + pendingInlineSuggestions.mTotalCount);
730             if (mSuggestionView == null || !pendingInlineSuggestions.mValid.get()) {
731                 return;
732             }
733             mSuggestionView.removeAllViews();
734             for (int i = 0; i < pendingInlineSuggestions.mTotalCount; i++) {
735                 View view = pendingInlineSuggestions.mViews[i];
736                 if (view == null) {
737                     continue;
738                 }
739                 mSuggestionView.addView(view);
740             }
741         }
742     }
743 
744     KeyboardLayoutView mView;
745 
onInputViewLayoutChanged(@onNull ImeLayoutInfo layoutInfo)746     private void onInputViewLayoutChanged(@NonNull ImeLayoutInfo layoutInfo) {
747         getTracer().onInputViewLayoutChanged(layoutInfo, () -> { });
748     }
749 
750     @Override
onCreateInputView()751     public View onCreateInputView() {
752         return getTracer().onCreateInputView(() -> {
753             mView = new KeyboardLayoutView(this, mSettings, this::onInputViewLayoutChanged);
754             return mView;
755         });
756     }
757 
758     @Override
onStartInput(EditorInfo editorInfo, boolean restarting)759     public void onStartInput(EditorInfo editorInfo, boolean restarting) {
760         getTracer().onStartInput(editorInfo, restarting,
761                 () -> super.onStartInput(editorInfo, restarting));
762     }
763 
764     @Override
onStartInputView(EditorInfo editorInfo, boolean restarting)765     public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
766         getTracer().onStartInputView(editorInfo, restarting,
767                 () -> super.onStartInputView(editorInfo, restarting));
768     }
769 
770     @Override
onFinishInputView(boolean finishingInput)771     public void onFinishInputView(boolean finishingInput) {
772         getTracer().onFinishInputView(finishingInput,
773                 () -> super.onFinishInputView(finishingInput));
774     }
775 
776     @Override
onFinishInput()777     public void onFinishInput() {
778         getTracer().onFinishInput(() -> super.onFinishInput());
779     }
780 
781     @Override
onKeyDown(int keyCode, KeyEvent event)782     public boolean onKeyDown(int keyCode, KeyEvent event) {
783         return getTracer().onKeyDown(keyCode, event, () -> super.onKeyDown(keyCode, event));
784     }
785 
786     @Override
onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo)787     public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
788         getTracer().onUpdateCursorAnchorInfo(cursorAnchorInfo,
789                 () -> super.onUpdateCursorAnchorInfo(cursorAnchorInfo));
790     }
791 
792     @Override
onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)793     public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
794             int candidatesStart, int candidatesEnd) {
795         getTracer().onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
796                 candidatesStart, candidatesEnd,
797                 () -> super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
798                         candidatesStart, candidatesEnd));
799     }
800 
801     @CallSuper
onEvaluateInputViewShown()802     public boolean onEvaluateInputViewShown() {
803         return getTracer().onEvaluateInputViewShown(() -> {
804             // onShowInputRequested() is indeed @CallSuper so we always call this, even when the
805             // result is ignored.
806             final boolean originalResult = super.onEvaluateInputViewShown();
807             if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
808                 final Configuration config = getResources().getConfiguration();
809                 if (config.keyboard != Configuration.KEYBOARD_NOKEYS
810                         && config.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES) {
811                     // Override the behavior of InputMethodService#onEvaluateInputViewShown()
812                     return true;
813                 }
814             }
815             return originalResult;
816         });
817     }
818 
819     @Override
820     public boolean onShowInputRequested(int flags, boolean configChange) {
821         return getTracer().onShowInputRequested(flags, configChange, () -> {
822             // onShowInputRequested() is not marked with @CallSuper, but just in case.
823             final boolean originalResult = super.onShowInputRequested(flags, configChange);
824             if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
825                 if ((flags & InputMethod.SHOW_EXPLICIT) == 0
826                         && getResources().getConfiguration().keyboard
827                         != Configuration.KEYBOARD_NOKEYS) {
828                     // Override the behavior of InputMethodService#onShowInputRequested()
829                     return true;
830                 }
831             }
832             return originalResult;
833         });
834     }
835 
836     @Override
837     public void onDestroy() {
838         getTracer().onDestroy(() -> {
839             super.onDestroy();
840             unregisterReceiver(mCommandReceiver);
841             mHandlerThread.quitSafely();
842         });
843     }
844 
845     @Override
846     public AbstractInputMethodImpl onCreateInputMethodInterface() {
847         return getTracer().onCreateInputMethodInterface(() -> new MockInputMethodImpl());
848     }
849 
850     private final ThreadLocal<Tracer> mThreadLocalTracer = new ThreadLocal<>();
851 
852     private Tracer getTracer() {
853         Tracer tracer = mThreadLocalTracer.get();
854         if (tracer == null) {
855             tracer = new Tracer(this);
856             mThreadLocalTracer.set(tracer);
857         }
858         return tracer;
859     }
860 
861     @NonNull
862     private ImeState getState() {
863         final boolean hasInputBinding = getCurrentInputBinding() != null;
864         final boolean hasFallbackInputConnection =
865                 !hasInputBinding
866                         || getCurrentInputConnection() == getCurrentInputBinding().getConnection();
867         return new ImeState(hasInputBinding, hasFallbackInputConnection);
868     }
869 
870     private PendingInlineSuggestions mPendingInlineSuggestions;
871 
872     private static final class PendingInlineSuggestions {
873         final InlineSuggestionsResponse mResponse;
874         final int mTotalCount;
875         final View[] mViews;
876         final AtomicInteger mInflatedViewCount;
877         final AtomicBoolean mValid = new AtomicBoolean(true);
878 
879         PendingInlineSuggestions(InlineSuggestionsResponse response) {
880             mResponse = response;
881             mTotalCount = response.getInlineSuggestions().size();
882             mViews = new View[mTotalCount];
883             mInflatedViewCount = new AtomicInteger(0);
884         }
885     }
886 
887     @MainThread
888     @Override
889     public InlineSuggestionsRequest onCreateInlineSuggestionsRequest(Bundle uiExtras) {
890         StylesBuilder stylesBuilder = UiVersions.newStylesBuilder();
891         stylesBuilder.addStyle(InlineSuggestionUi.newStyleBuilder().build());
892         Bundle styles = stylesBuilder.build();
893 
894         if (mInlineSuggestionsExtras != null) {
895             styles.putAll(mInlineSuggestionsExtras);
896         }
897 
898         return getTracer().onCreateInlineSuggestionsRequest(() -> {
899             final ArrayList<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
900             presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
901                     new Size(400, 100)).setStyle(styles).build());
902             presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
903                     new Size(400, 100)).setStyle(styles).build());
904 
905             final InlinePresentationSpec tooltipSpec =
906                     new InlinePresentationSpec.Builder(new Size(100, 100),
907                             new Size(400, 100)).setStyle(styles).build();
908             final InlineSuggestionsRequest.Builder builder =
909                     new InlineSuggestionsRequest.Builder(presentationSpecs)
910                             .setInlineTooltipPresentationSpec(tooltipSpec)
911                             .setMaxSuggestionCount(6);
912             if (mInlineSuggestionsExtras != null) {
913                 builder.setExtras(mInlineSuggestionsExtras.deepCopy());
914             }
915             return builder.build();
916         });
917     }
918 
919     @MainThread
920     @Override
921     public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
922         return getTracer().onInlineSuggestionsResponse(response, () -> {
923             final PendingInlineSuggestions pendingInlineSuggestions =
924                     new PendingInlineSuggestions(response);
925             if (mPendingInlineSuggestions != null) {
926                 mPendingInlineSuggestions.mValid.set(false);
927             }
928             mPendingInlineSuggestions = pendingInlineSuggestions;
929             if (pendingInlineSuggestions.mTotalCount == 0) {
930                 if (mView != null) {
931                     mView.updateInlineSuggestions(pendingInlineSuggestions);
932                 }
933                 return true;
934             }
935 
936             final ExecutorService executorService = Executors.newCachedThreadPool();
937             for (int i = 0; i < pendingInlineSuggestions.mTotalCount; i++) {
938                 final int index = i;
939                 InlineSuggestion inlineSuggestion =
940                         pendingInlineSuggestions.mResponse.getInlineSuggestions().get(index);
941                 inlineSuggestion.inflate(
942                         this,
943                         new Size(WRAP_CONTENT, WRAP_CONTENT),
944                         executorService,
945                         suggestionView -> {
946                             Log.d(TAG, "new inline suggestion view ready");
947                             if (suggestionView != null) {
948                                 suggestionView.setOnClickListener((v) -> {
949                                     getTracer().onInlineSuggestionClickedEvent(() -> { });
950                                 });
951                                 suggestionView.setOnLongClickListener((v) -> {
952                                     getTracer().onInlineSuggestionLongClickedEvent(() -> { });
953                                     return true;
954                                 });
955                                 pendingInlineSuggestions.mViews[index] = suggestionView;
956                             }
957                             if (pendingInlineSuggestions.mInflatedViewCount.incrementAndGet()
958                                     == pendingInlineSuggestions.mTotalCount
959                                     && pendingInlineSuggestions.mValid.get()) {
960                                 Log.d(TAG, "ready to display all suggestions");
961                                 mMainHandler.post(() ->
962                                         mView.updateInlineSuggestions(pendingInlineSuggestions));
963                             }
964                         });
965             }
966             return true;
967         });
968     }
969 
970     /**
971      * Event tracing helper class for {@link MockIme}.
972      */
973     private static final class Tracer {
974 
975         @NonNull
976         private final MockIme mIme;
977 
978         private final int mThreadId = Process.myTid();
979 
980         @NonNull
981         private final String mThreadName =
982                 Thread.currentThread().getName() != null ? Thread.currentThread().getName() : "";
983 
984         private final boolean mIsMainThread =
985                 Looper.getMainLooper().getThread() == Thread.currentThread();
986 
987         private int mNestLevel = 0;
988 
989         private String mImeEventActionName;
990 
991         private String mClientPackageName;
992 
993         Tracer(@NonNull MockIme mockIme) {
994             mIme = mockIme;
995         }
996 
997         private void sendEventInternal(@NonNull ImeEvent event) {
998             if (mImeEventActionName == null) {
999                 mImeEventActionName = mIme.getImeEventActionName();
1000             }
1001             if (mClientPackageName == null) {
1002                 mClientPackageName = mIme.getClientPackageName();
1003             }
1004             if (mImeEventActionName == null || mClientPackageName == null) {
1005                 Log.e(TAG, "Tracer cannot be used before onCreate()");
1006                 return;
1007             }
1008             final Intent intent = new Intent()
1009                     .setAction(mImeEventActionName)
1010                     .setPackage(mClientPackageName)
1011                     .putExtras(event.toBundle())
1012                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
1013                             | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
1014             mIme.sendBroadcast(intent);
1015         }
1016 
1017         private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable) {
1018             recordEventInternal(eventName, runnable, new Bundle());
1019         }
1020 
1021         private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable,
1022                 @NonNull Bundle arguments) {
1023             recordEventInternal(eventName, () -> {
1024                 runnable.run(); return ImeEvent.RETURN_VALUE_UNAVAILABLE;
1025             }, arguments);
1026         }
1027 
1028         private <T> T recordEventInternal(@NonNull String eventName,
1029                 @NonNull Supplier<T> supplier) {
1030             return recordEventInternal(eventName, supplier, new Bundle());
1031         }
1032 
1033         private <T> T recordEventInternal(@NonNull String eventName,
1034                 @NonNull Supplier<T> supplier, @NonNull Bundle arguments) {
1035             final ImeState enterState = mIme.getState();
1036             final long enterTimestamp = SystemClock.elapsedRealtimeNanos();
1037             final long enterWallTime = System.currentTimeMillis();
1038             final int nestLevel = mNestLevel;
1039             // Send enter event
1040             sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
1041                     mThreadId, mIsMainThread, enterTimestamp, 0, enterWallTime,
1042                     0, enterState, null, arguments,
1043                     ImeEvent.RETURN_VALUE_UNAVAILABLE));
1044             ++mNestLevel;
1045             T result;
1046             try {
1047                 result = supplier.get();
1048             } finally {
1049                 --mNestLevel;
1050             }
1051             final long exitTimestamp = SystemClock.elapsedRealtimeNanos();
1052             final long exitWallTime = System.currentTimeMillis();
1053             final ImeState exitState = mIme.getState();
1054             // Send exit event
1055             sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
1056                     mThreadId, mIsMainThread, enterTimestamp, exitTimestamp, enterWallTime,
1057                     exitWallTime, enterState, exitState, arguments, result));
1058             return result;
1059         }
1060 
1061         void onCreate(@NonNull Runnable runnable) {
1062             recordEventInternal("onCreate", runnable);
1063         }
1064 
1065         void onVerify(String name, @NonNull BooleanSupplier supplier) {
1066             final Bundle arguments = new Bundle();
1067             arguments.putString("name", name);
1068             recordEventInternal("onVerify", supplier::getAsBoolean, arguments);
1069         }
1070 
1071         void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly,
1072                 @NonNull Runnable runnable) {
1073             final Bundle arguments = new Bundle();
1074             arguments.putBoolean("isFullscreen", isFullscreen);
1075             arguments.putBoolean("isCandidatesOnly", isCandidatesOnly);
1076             recordEventInternal("onConfigureWindow", runnable, arguments);
1077         }
1078 
1079         boolean onEvaluateFullscreenMode(@NonNull BooleanSupplier supplier) {
1080             return recordEventInternal("onEvaluateFullscreenMode", supplier::getAsBoolean);
1081         }
1082 
1083         boolean onEvaluateInputViewShown(@NonNull BooleanSupplier supplier) {
1084             return recordEventInternal("onEvaluateInputViewShown", supplier::getAsBoolean);
1085         }
1086 
1087         View onCreateInputView(@NonNull Supplier<View> supplier) {
1088             return recordEventInternal("onCreateInputView", supplier);
1089         }
1090 
1091         void onStartInput(EditorInfo editorInfo, boolean restarting, @NonNull Runnable runnable) {
1092             final Bundle arguments = new Bundle();
1093             arguments.putParcelable("editorInfo", editorInfo);
1094             arguments.putBoolean("restarting", restarting);
1095             recordEventInternal("onStartInput", runnable, arguments);
1096         }
1097 
1098         void onWindowVisibilityChanged(@NonNull Runnable runnable, int visibility) {
1099             final Bundle arguments = new Bundle();
1100             arguments.putInt("visible", visibility);
1101             recordEventInternal("onWindowVisibilityChanged", runnable, arguments);
1102         }
1103 
1104         void onStartInputView(EditorInfo editorInfo, boolean restarting,
1105                 @NonNull Runnable runnable) {
1106             final Bundle arguments = new Bundle();
1107             arguments.putParcelable("editorInfo", editorInfo);
1108             arguments.putBoolean("restarting", restarting);
1109             recordEventInternal("onStartInputView", runnable, arguments);
1110         }
1111 
1112         void onFinishInputView(boolean finishingInput, @NonNull Runnable runnable) {
1113             final Bundle arguments = new Bundle();
1114             arguments.putBoolean("finishingInput", finishingInput);
1115             recordEventInternal("onFinishInputView", runnable, arguments);
1116         }
1117 
1118         void onFinishInput(@NonNull Runnable runnable) {
1119             recordEventInternal("onFinishInput", runnable);
1120         }
1121 
1122         boolean onKeyDown(int keyCode, KeyEvent event, @NonNull BooleanSupplier supplier) {
1123             final Bundle arguments = new Bundle();
1124             arguments.putInt("keyCode", keyCode);
1125             arguments.putParcelable("event", event);
1126             return recordEventInternal("onKeyDown", supplier::getAsBoolean, arguments);
1127         }
1128 
1129         void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo,
1130                 @NonNull Runnable runnable) {
1131             final Bundle arguments = new Bundle();
1132             arguments.putParcelable("cursorAnchorInfo", cursorAnchorInfo);
1133             recordEventInternal("onUpdateCursorAnchorInfo", runnable, arguments);
1134         }
1135 
1136         void onUpdateSelection(int oldSelStart,
1137                 int oldSelEnd,
1138                 int newSelStart,
1139                 int newSelEnd,
1140                 int candidatesStart,
1141                 int candidatesEnd,
1142                 @NonNull Runnable runnable) {
1143             final Bundle arguments = new Bundle();
1144             arguments.putInt("oldSelStart", oldSelStart);
1145             arguments.putInt("oldSelEnd", oldSelEnd);
1146             arguments.putInt("newSelStart", newSelStart);
1147             arguments.putInt("newSelEnd", newSelEnd);
1148             arguments.putInt("candidatesStart", candidatesStart);
1149             arguments.putInt("candidatesEnd", candidatesEnd);
1150             recordEventInternal("onUpdateSelection", runnable, arguments);
1151         }
1152 
1153         boolean onShowInputRequested(int flags, boolean configChange,
1154                 @NonNull BooleanSupplier supplier) {
1155             final Bundle arguments = new Bundle();
1156             arguments.putInt("flags", flags);
1157             arguments.putBoolean("configChange", configChange);
1158             return recordEventInternal("onShowInputRequested", supplier::getAsBoolean, arguments);
1159         }
1160 
1161         void onDestroy(@NonNull Runnable runnable) {
1162             recordEventInternal("onDestroy", runnable);
1163         }
1164 
1165         void attachToken(IBinder token, @NonNull Runnable runnable) {
1166             final Bundle arguments = new Bundle();
1167             arguments.putBinder("token", token);
1168             recordEventInternal("attachToken", runnable, arguments);
1169         }
1170 
1171         void bindInput(InputBinding binding, @NonNull Runnable runnable) {
1172             final Bundle arguments = new Bundle();
1173             arguments.putParcelable("binding", binding);
1174             recordEventInternal("bindInput", runnable, arguments);
1175         }
1176 
1177         void unbindInput(@NonNull Runnable runnable) {
1178             recordEventInternal("unbindInput", runnable);
1179         }
1180 
1181         void showSoftInput(int flags, ResultReceiver resultReceiver, @NonNull Runnable runnable) {
1182             final Bundle arguments = new Bundle();
1183             arguments.putInt("flags", flags);
1184             arguments.putParcelable("resultReceiver", resultReceiver);
1185             recordEventInternal("showSoftInput", runnable, arguments);
1186         }
1187 
1188         void hideSoftInput(int flags, ResultReceiver resultReceiver, @NonNull Runnable runnable) {
1189             final Bundle arguments = new Bundle();
1190             arguments.putInt("flags", flags);
1191             arguments.putParcelable("resultReceiver", resultReceiver);
1192             recordEventInternal("hideSoftInput", runnable, arguments);
1193         }
1194 
1195         AbstractInputMethodImpl onCreateInputMethodInterface(
1196                 @NonNull Supplier<AbstractInputMethodImpl> supplier) {
1197             return recordEventInternal("onCreateInputMethodInterface", supplier);
1198         }
1199 
1200         void onReceiveCommand(@NonNull ImeCommand command, @NonNull Runnable runnable) {
1201             final Bundle arguments = new Bundle();
1202             arguments.putBundle("command", command.toBundle());
1203             recordEventInternal("onReceiveCommand", runnable, arguments);
1204         }
1205 
1206         void onHandleCommand(
1207                 @NonNull ImeCommand command, @NonNull Supplier<Object> resultSupplier) {
1208             final Bundle arguments = new Bundle();
1209             arguments.putBundle("command", command.toBundle());
1210             recordEventInternal("onHandleCommand", resultSupplier, arguments);
1211         }
1212 
1213         void onInputViewLayoutChanged(@NonNull ImeLayoutInfo imeLayoutInfo,
1214                 @NonNull Runnable runnable) {
1215             final Bundle arguments = new Bundle();
1216             imeLayoutInfo.writeToBundle(arguments);
1217             recordEventInternal("onInputViewLayoutChanged", runnable, arguments);
1218         }
1219 
1220         void onStrictModeViolated(@NonNull Runnable runnable) {
1221             final Bundle arguments = new Bundle();
1222             recordEventInternal("onStrictModeViolated", runnable, arguments);
1223         }
1224 
1225         InlineSuggestionsRequest onCreateInlineSuggestionsRequest(
1226                 @NonNull Supplier<InlineSuggestionsRequest> supplier) {
1227             return recordEventInternal("onCreateInlineSuggestionsRequest", supplier);
1228         }
1229 
1230         boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response,
1231                 @NonNull BooleanSupplier supplier) {
1232             final Bundle arguments = new Bundle();
1233             arguments.putParcelable("response", response);
1234             return recordEventInternal("onInlineSuggestionsResponse", supplier::getAsBoolean,
1235                     arguments);
1236         }
1237 
1238         void onInlineSuggestionClickedEvent(@NonNull Runnable runnable) {
1239             final Bundle arguments = new Bundle();
1240             recordEventInternal("onInlineSuggestionClickedEvent", runnable, arguments);
1241         }
1242 
1243         void onInlineSuggestionLongClickedEvent(@NonNull Runnable runnable) {
1244             final Bundle arguments = new Bundle();
1245             recordEventInternal("onInlineSuggestionLongClickedEvent", runnable, arguments);
1246         }
1247     }
1248 }
1249