1 /*
2  * Copyright (C) 2020 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 android.view.inputmethod.cts;
18 
19 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
20 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
21 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
22 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
23 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
24 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
25 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
26 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
27 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
28 import static android.view.inputmethod.cts.util.TestUtils.isInputMethodPickerShown;
29 
30 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
31 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
32 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
33 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
34 import static com.android.cts.mockime.ImeEventStreamTestUtils.showSoftInputMatcher;
35 
36 import static org.junit.Assert.assertNotNull;
37 import static org.junit.Assert.assertTrue;
38 
39 import android.app.Activity;
40 import android.content.Intent;
41 import android.content.res.Configuration;
42 import android.graphics.Color;
43 import android.graphics.PixelFormat;
44 import android.graphics.Point;
45 import android.platform.test.annotations.AppModeFull;
46 import android.platform.test.annotations.AppModeSdkSandbox;
47 import android.util.Pair;
48 import android.view.Gravity;
49 import android.view.View;
50 import android.view.WindowInsets;
51 import android.view.WindowManager;
52 import android.view.inputmethod.InputMethod;
53 import android.view.inputmethod.InputMethodManager;
54 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
55 import android.view.inputmethod.cts.util.TestActivity;
56 import android.view.inputmethod.cts.util.TestUtils;
57 import android.view.inputmethod.cts.util.UnlockScreenRule;
58 import android.widget.EditText;
59 import android.widget.LinearLayout;
60 import android.widget.TextView;
61 
62 import androidx.annotation.AnyThread;
63 import androidx.annotation.NonNull;
64 import androidx.test.filters.MediumTest;
65 import androidx.test.platform.app.InstrumentationRegistry;
66 import androidx.test.runner.AndroidJUnit4;
67 
68 import com.android.compatibility.common.util.PollingCheck;
69 import com.android.cts.mockime.ImeEventStream;
70 import com.android.cts.mockime.ImeSettings;
71 import com.android.cts.mockime.MockImeSession;
72 
73 import org.junit.Assume;
74 import org.junit.Rule;
75 import org.junit.Test;
76 import org.junit.runner.RunWith;
77 
78 import java.util.concurrent.TimeUnit;
79 import java.util.concurrent.atomic.AtomicReference;
80 
81 @MediumTest
82 @RunWith(AndroidJUnit4.class)
83 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
84 public class ImeInsetsVisibilityTest extends EndToEndImeTestBase {
85     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
86     private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
87     private static final int NEW_KEYBOARD_HEIGHT = 300;
88 
89     @Rule
90     public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
91 
92     @Test
testImeVisibilityWhenImeFocusableChildPopup()93     public void testImeVisibilityWhenImeFocusableChildPopup() throws Exception {
94         Assume.assumeFalse(isPreventImeStartup());
95         final InputMethodManager imm = getImmOrFail();
96 
97         try (MockImeSession imeSession = MockImeSession.create(
98                 InstrumentationRegistry.getInstrumentation().getContext(),
99                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
100                 new ImeSettings.Builder())) {
101             final ImeEventStream stream = imeSession.openEventStream();
102 
103             final String marker = getTestMarker();
104             final Pair<EditText, TestActivity> editTextTestActivityPair =
105                     launchTestActivity(false, marker);
106             final EditText editText = editTextTestActivityPair.first;
107             final TestActivity activity = editTextTestActivityPair.second;
108 
109             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
110             expectImeInvisible(TIMEOUT);
111 
112             assertTrue("showSoftInput must success if the View has IME focus", getOnMainSync(
113                     () -> editText.requestFocus() && imm.showSoftInput(editText, 0)));
114 
115             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
116             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
117             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
118             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
119                     View.VISIBLE, TIMEOUT);
120             PollingCheck.check("Ime insets should be visible", TIMEOUT,
121                     () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
122             expectImeVisible(TIMEOUT);
123 
124             try (ChildWindowHolder childWindow = createChildTransparentApplicationWindowOnMain(
125                     activity, 200 /* width */, 200 /* height */,
126                     FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM,
127                     WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
128                             | WindowInsets.Type.navigationBars())) {
129                 // The window will be shown above (in y-axis) the IME.
130                 TestUtils.runOnMainSync(
131                         () -> childWindow.getRootView().setVisibility(View.VISIBLE));
132                 TestUtils.waitOnMainUntil(
133                         () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
134                         TIMEOUT, "Ime insets should be visible");
135                 expectImeVisible(TIMEOUT);
136             }
137         }
138     }
139 
140     @Test
testImeVisibilityWhenImeFocusableGravityBottomChildPopup()141     public void testImeVisibilityWhenImeFocusableGravityBottomChildPopup() throws Exception {
142         Assume.assumeFalse(isPreventImeStartup());
143         final InputMethodManager imm = getImmOrFail();
144 
145         try (MockImeSession imeSession = MockImeSession.create(
146                 InstrumentationRegistry.getInstrumentation().getContext(),
147                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
148                 new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
149             final ImeEventStream stream = imeSession.openEventStream();
150 
151             final String marker = getTestMarker();
152             final Pair<EditText, TestActivity> editTextTestActivityPair =
153                     launchTestActivity(false, marker);
154             final EditText editText = editTextTestActivityPair.first;
155             final TestActivity activity = editTextTestActivityPair.second;
156 
157             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
158             expectImeInvisible(TIMEOUT);
159 
160             assertTrue("showSoftInput must success if the View has IME focus", getOnMainSync(
161                     () -> editText.requestFocus() && imm.showSoftInput(editText, 0)));
162 
163             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
164             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
165             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
166             PollingCheck.check("Ime insets should be visible", TIMEOUT,
167                     () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
168             expectImeVisible(TIMEOUT);
169 
170             try (ChildWindowHolder childWindow = createChildBottomPanelWindowOnMain(activity,
171                     MATCH_PARENT /* width */, NEW_KEYBOARD_HEIGHT /* height */,
172                     FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)) {
173                 // The window will be shown above (in y-axis) the IME.
174                 TestUtils.runOnMainSync(() -> {
175                     childWindow.getRootView().setBackgroundColor(Color.RED);
176                     childWindow.getRootView().setVisibility(View.VISIBLE);
177                 });
178                 // IME should be on screen without reset.
179                 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
180 
181                 TestUtils.waitOnMainUntil(
182                         () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
183                         TIMEOUT, "Ime insets should be visible");
184                 expectImeVisible(TIMEOUT);
185             }
186         }
187     }
188 
189     @Test
testImeVisibilityWhenImeFocusableChildPopupOverlaps()190     public void testImeVisibilityWhenImeFocusableChildPopupOverlaps() throws Exception {
191         Assume.assumeFalse(isPreventImeStartup());
192         final InputMethodManager imm = getImmOrFail();
193 
194         try (MockImeSession imeSession = MockImeSession.create(
195                 InstrumentationRegistry.getInstrumentation().getContext(),
196                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
197                 new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
198             final ImeEventStream stream = imeSession.openEventStream();
199 
200             final String marker = getTestMarker();
201             final Pair<EditText, TestActivity> editTextTestActivityPair =
202                     launchTestActivity(false, marker);
203             final EditText editText = editTextTestActivityPair.first;
204             final TestActivity activity = editTextTestActivityPair.second;
205 
206             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
207             expectImeInvisible(TIMEOUT);
208 
209             assertTrue("showSoftInput must success if the View has IME focus", getOnMainSync(
210                     () -> editText.requestFocus() && imm.showSoftInput(editText, 0)));
211 
212             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
213             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
214             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
215             PollingCheck.check("Ime insets should be visible", TIMEOUT,
216                     () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
217             expectImeVisible(TIMEOUT);
218 
219             try (ChildWindowHolder childWindow = createChildBottomPanelWindowOnMain(activity,
220                     MATCH_PARENT /* width */, NEW_KEYBOARD_HEIGHT /* height */,
221                     FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM | FLAG_LAYOUT_IN_SCREEN)) {
222                 // The window will be shown behind (in z-axis) the IME.
223                 TestUtils.runOnMainSync(() -> {
224                     childWindow.getRootView().setBackgroundColor(Color.RED);
225                     childWindow.getRootView().setVisibility(View.VISIBLE);
226                 });
227                 // IME should be on screen without reset.
228                 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
229 
230                 TestUtils.waitOnMainUntil(
231                         () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
232                         TIMEOUT, "Ime insets should be visible");
233                 expectImeVisible(TIMEOUT);
234             }
235         }
236     }
237 
238     @AppModeFull(reason = "Instant apps cannot rely on ACTION_CLOSE_SYSTEM_DIALOGS")
239     @Test
testEditTextPositionAndPersistWhenAboveImeWindowShown()240     public void testEditTextPositionAndPersistWhenAboveImeWindowShown() throws Exception {
241         Assume.assumeFalse(isPreventImeStartup());
242         final InputMethodManager imm = getImmOrFail();
243 
244         try (MockImeSession imeSession = MockImeSession.create(
245                 InstrumentationRegistry.getInstrumentation().getContext(),
246                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
247                 new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
248             final ImeEventStream stream = imeSession.openEventStream();
249 
250             final String marker = getTestMarker();
251             final Pair<EditText, TestActivity> editTextTestActivityPair =
252                     launchTestActivity(true, marker);
253             final EditText editText = editTextTestActivityPair.first;
254             final TestActivity activity = editTextTestActivityPair.second;
255             final WindowInsets[] insetsFromActivity = new WindowInsets[1];
256             Point curEditPos = getLocationOnScreenForView(editText);
257 
258             TestUtils.runOnMainSync(() -> {
259                 activity.getWindow().getDecorView().setOnApplyWindowInsetsListener(
260                         (v, insets) -> insetsFromActivity[0] = insets);
261             });
262 
263             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
264             expectImeInvisible(TIMEOUT);
265 
266             assertTrue("showSoftInput must success if the View has IME focus", getOnMainSync(
267                     () -> editText.requestFocus() && imm.showSoftInput(editText, 0)));
268 
269             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
270             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
271             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
272             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
273                     View.VISIBLE, TIMEOUT);
274             expectImeVisible(TIMEOUT);
275 
276             Point lastEditTextPos = new Point(curEditPos);
277             curEditPos = getLocationOnScreenForView(editText);
278             // Watch doesn't support navigation bar and has limited screen size, so no transition
279             // in editbox with respect to x and y coordinates
280             Configuration config = InstrumentationRegistry.getInstrumentation()
281                     .getContext()
282                     .getResources()
283                     .getConfiguration();
284             boolean isSmallScreenLayout =
285                     config.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_SMALL);
286 
287             if (isSmallScreenLayout) {
288                 assertTrue("Insets should visible",
289                         isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime()));
290             } else {
291                 assertTrue("Insets should visible and EditText position should be adjusted",
292                         isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime())
293                                 && curEditPos.y < lastEditTextPos.y);
294             }
295 
296             imm.showInputMethodPicker();
297             TestUtils.waitOnMainUntil(() -> isInputMethodPickerShown(imm) && editText.isLaidOut(),
298                     TIMEOUT, "InputMethod picker should be shown");
299             lastEditTextPos = new Point(curEditPos);
300             curEditPos = getLocationOnScreenForView(editText);
301 
302             assertTrue("Insets visibility & EditText position should persist when "
303                             + "the above IME window shown",
304                     isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime())
305                             && curEditPos.equals(lastEditTextPos));
306 
307             InstrumentationRegistry.getInstrumentation().getContext().sendBroadcast(
308                     new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
309             TestUtils.waitOnMainUntil(() -> !isInputMethodPickerShown(imm), TIMEOUT,
310                     "InputMethod picker should be closed");
311         }
312     }
313 
314     /**
315      * Test the IME window won't cover the editor when the app creates a panel window to receive
316      * the IME insets.
317      *
318      * <p>Regression test for Bug 195765264 and Bug 152304051.</p>
319      */
320     @Test
testEditorWontCoveredByImeWhenInputWindowBehindPanel()321     public void testEditorWontCoveredByImeWhenInputWindowBehindPanel() throws Exception {
322         try (MockImeSession imeSession = MockImeSession.create(
323                 InstrumentationRegistry.getInstrumentation().getContext(),
324                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
325                 new ImeSettings.Builder())) {
326             final ImeEventStream stream = imeSession.openEventStream();
327             final String marker = getTestMarker();
328             // Launch a test activity with SOFT_INPUT_ADJUST_NOTHING to not resize by IME insets.
329             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
330             final TestActivity testActivity = TestActivity.startSync(activity -> {
331                 final LinearLayout layout = new LinearLayout(activity);
332                 layout.setOrientation(LinearLayout.VERTICAL);
333                 layout.setGravity(Gravity.BOTTOM);
334                 final EditText editText = new EditText(activity);
335                 editText.setHint("focused editText");
336                 editText.setPrivateImeOptions(marker);
337                 // Initial editor visibility as GONE for testing IME visibility controlled by panel.
338                 editText.setVisibility(View.GONE);
339                 editTextRef.set(editText);
340                 layout.addView(editText);
341                 activity.getWindow().setSoftInputMode(
342                         WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
343                 return layout;
344             });
345             final EditText editText = editTextRef.get();
346             // Create a panel window to receive IME insets for adjusting editText position.
347             final View panelView = TestUtils.getOnMainSync(() -> {
348                 final View panel = new View(testActivity);
349                 panel.setOnApplyWindowInsetsListener((v, insets) -> {
350                     if (insets.isVisible(WindowInsets.Type.ime())) {
351                         // Request editText focused when IME insets visible.
352                         editText.setVisibility(View.VISIBLE);
353                         editText.requestFocus();
354                         LinearLayout.LayoutParams lp =
355                                 (LinearLayout.LayoutParams) editText.getLayoutParams();
356                         lp.setMargins(0, 0, 0, editText.getRootView().getMeasuredHeight()
357                                 - panel.getMeasuredHeight());
358                         editText.requestLayout();
359                     } else {
360                         // Clear editText focused when IME insets invisible.
361                         editText.clearFocus();
362                         editText.setVisibility(View.GONE);
363                     }
364                     return insets;
365                 });
366                 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
367                         1, MATCH_PARENT,
368                         0, 0, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,
369                         WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
370                                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
371                                 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
372                         PixelFormat.TRANSLUCENT);
373                 lp.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.systemBars());
374                 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
375                 lp.token = testActivity.getWindow().getDecorView().getWindowToken();
376                 testActivity.getWindowManager().addView(panel, lp);
377                 return panel;
378             });
379             notExpectEvent(stream, editorMatcher("onStartInputView", marker), NOT_EXPECT_TIMEOUT);
380             expectImeInvisible(TIMEOUT);
381             // Show IME by using WindowInsets API.
382             testActivity.getWindow().getInsetsController().show(WindowInsets.Type.ime());
383             TestUtils.waitOnMainUntil(() -> isInsetsVisible(panelView.getRootWindowInsets(),
384                     WindowInsets.Type.ime()), TIMEOUT, "The panel should receive IME insets");
385             TestUtils.waitOnMainUntil(
386                     () -> editText.getVisibility() == View.VISIBLE && editText.hasFocus(),
387                     TIMEOUT, "The editor should be shown and visible");
388             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
389             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
390             expectImeVisible(TIMEOUT);
391         }
392     }
393 
isInsetsVisible(WindowInsets winInsets, int type)394     private boolean isInsetsVisible(WindowInsets winInsets, int type) {
395         if (winInsets == null) {
396             return false;
397         }
398         return winInsets.isVisible(type);
399     }
400 
getLocationOnScreenForView(View view)401     private Point getLocationOnScreenForView(View view) {
402         return TestUtils.getOnMainSync(() -> {
403             final int[] tmpPos = new int[2];
404             view.getLocationOnScreen(tmpPos);
405             return new Point(tmpPos[0], tmpPos[1]);
406         });
407     }
408 
launchTestActivity(boolean useDialogTheme, @NonNull String focusedMarker)409     private Pair<EditText, TestActivity> launchTestActivity(boolean useDialogTheme,
410             @NonNull String focusedMarker) {
411         final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
412         final AtomicReference<TestActivity> testActivityRef = new AtomicReference<>();
413 
414         TestActivity.startSync(activity -> {
415             final LinearLayout layout = new LinearLayout(activity);
416             layout.setOrientation(LinearLayout.VERTICAL);
417             layout.setGravity(Gravity.BOTTOM);
418             if (useDialogTheme) {
419                 // Create a floating Dialog
420                 activity.setTheme(android.R.style.Theme_Material_Dialog);
421                 TextView textView = new TextView(activity);
422                 textView.setText("I'm a TextView");
423                 textView.setHeight(activity.getWindowManager().getMaximumWindowMetrics()
424                         .getBounds().height() / 3);
425                 layout.addView(textView);
426             }
427 
428             final EditText focusedEditText = new EditText(activity);
429             focusedEditText.setHint("focused editText");
430             focusedEditText.setPrivateImeOptions(focusedMarker);
431 
432             focusedEditTextRef.set(focusedEditText);
433             testActivityRef.set(activity);
434 
435             layout.addView(focusedEditText);
436             return layout;
437         });
438         return new Pair<>(focusedEditTextRef.get(), testActivityRef.get());
439     }
440 
441     /**
442      * A utility class to pack the root {@link View} and its clean-up operation that is compatible
443      * with {@link AutoCloseable} protocol.
444      */
445     private static final class ChildWindowHolder implements AutoCloseable {
446         @NonNull
447         private final View mRootView;
448 
ChildWindowHolder(@onNull View rootView)449         private ChildWindowHolder(@NonNull View rootView) {
450             mRootView = rootView;
451         }
452 
453         @NonNull
454         @AnyThread
getRootView()455         View getRootView() {
456             return mRootView;
457         }
458 
459         @Override
close()460         public void close() {
461             TestUtils.runOnMainSync(() -> mRootView.getContext()
462                     .getSystemService(WindowManager.class).removeView(mRootView));
463         }
464     }
465 
466     @NonNull
createChildBottomPanelWindowOnMain(Activity activity, int width, int height, int windowFlags)467     private ChildWindowHolder createChildBottomPanelWindowOnMain(Activity activity, int width,
468             int height, int windowFlags) {
469         return TestUtils.getOnMainSync(() -> {
470             final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
471             attrs.token = null;
472             attrs.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
473             attrs.width = width;
474             attrs.height = height;
475             attrs.gravity = Gravity.BOTTOM;
476             attrs.flags = windowFlags;
477             final View childViewRoot = new View(activity);
478             activity.getSystemService(WindowManager.class).addView(childViewRoot, attrs);
479             return new ChildWindowHolder(childViewRoot);
480         });
481     }
482 
483     @NonNull
484     private ChildWindowHolder createChildTransparentApplicationWindowOnMain(Activity activity,
485             int width, int height, int windowFlags, int fitInsetsTypes) {
486         return TestUtils.getOnMainSync(() -> {
487             final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
488             attrs.token = activity.getWindow().getAttributes().token;
489             attrs.type = WindowManager.LayoutParams.TYPE_APPLICATION;
490             attrs.width = width;
491             attrs.height = height;
492             attrs.format = PixelFormat.TRANSPARENT;
493             attrs.gravity = Gravity.NO_GRAVITY;
494             attrs.flags = windowFlags;
495             attrs.setFitInsetsTypes(fitInsetsTypes);
496             final View childViewRoot = new View(activity);
497             activity.getSystemService(WindowManager.class).addView(childViewRoot, attrs);
498             return new ChildWindowHolder(childViewRoot);
499         });
500     }
501 
502     @NonNull
503     private static InputMethodManager getImmOrFail() {
504         final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
505                 .getTargetContext().getSystemService(InputMethodManager.class);
506         assertNotNull(imm);
507         return imm;
508     }
509 }
510