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