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