1 /*
2  * Copyright (C) 2019 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.server.wm;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.server.wm.UiDeviceUtils.pressSleepButton;
21 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
22 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
23 import static android.server.wm.WindowManagerState.STATE_RESUMED;
24 import static android.server.wm.app.Components.HOME_ACTIVITY;
25 import static android.server.wm.app.Components.SECONDARY_HOME_ACTIVITY;
26 import static android.server.wm.app.Components.SINGLE_HOME_ACTIVITY;
27 import static android.server.wm.app.Components.SINGLE_SECONDARY_HOME_ACTIVITY;
28 import static android.server.wm.app.Components.TEST_LIVE_WALLPAPER_SERVICE;
29 import static android.server.wm.app.Components.TestLiveWallpaperKeys.COMPONENT;
30 import static android.server.wm.app.Components.TestLiveWallpaperKeys.ENGINE_DISPLAY_ID;
31 import static android.server.wm.BarTestUtils.assumeHasBars;
32 import static android.server.wm.MockImeHelper.createManagedMockImeSession;
33 import static android.view.Display.DEFAULT_DISPLAY;
34 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
35 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
36 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
37 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
38 
39 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
40 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
41 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
42 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
43 
44 import static com.google.common.truth.Truth.assertThat;
45 
46 import static org.junit.Assert.assertEquals;
47 import static org.junit.Assert.assertFalse;
48 import static org.junit.Assert.assertTrue;
49 import static org.junit.Assume.assumeFalse;
50 import static org.junit.Assume.assumeTrue;
51 
52 import android.app.Activity;
53 import android.app.WallpaperManager;
54 import android.content.ComponentName;
55 import android.content.Context;
56 import android.content.ContextWrapper;
57 import android.content.Intent;
58 import android.content.res.Configuration;
59 import android.graphics.Bitmap;
60 import android.graphics.Canvas;
61 import android.graphics.Color;
62 import android.graphics.Rect;
63 import android.os.Bundle;
64 import android.os.SystemClock;
65 import android.platform.test.annotations.Presubmit;
66 import android.server.wm.WindowManagerState.DisplayContent;
67 import android.server.wm.TestJournalProvider.TestJournalContainer;
68 import android.server.wm.WindowManagerState.WindowState;
69 import android.server.wm.intent.Activities;
70 import android.text.TextUtils;
71 import android.view.WindowManager;
72 import android.view.inputmethod.EditorInfo;
73 import android.view.inputmethod.InputConnection;
74 import android.view.inputmethod.InputMethodManager;
75 import android.widget.EditText;
76 import android.widget.LinearLayout;
77 
78 import com.android.compatibility.common.util.ImeAwareEditText;
79 import com.android.compatibility.common.util.SystemUtil;
80 import com.android.compatibility.common.util.TestUtils;
81 import com.android.cts.mockime.ImeCommand;
82 import com.android.cts.mockime.ImeEventStream;
83 import com.android.cts.mockime.MockImeSession;
84 
85 import org.junit.Before;
86 import org.junit.Test;
87 
88 import java.util.List;
89 import java.util.concurrent.TimeUnit;
90 
91 /**
92  * Build/Install/Run:
93  *     atest CtsWindowManagerDeviceTestCases:MultiDisplaySystemDecorationTests
94  *
95  * This tests that verify the following should not be run for OEM device verification:
96  * Wallpaper added if display supports system decorations (and not added otherwise)
97  * Navigation bar is added if display supports system decorations (and not added otherwise)
98  * Secondary Home is shown if display supports system decorations (and not shown otherwise)
99  * IME is shown if display supports system decorations (and not shown otherwise)
100  */
101 @Presubmit
102 @android.server.wm.annotation.Group3
103 public class MultiDisplaySystemDecorationTests extends MultiDisplayTestBase {
104 
105     @Before
106     @Override
setUp()107     public void setUp() throws Exception {
108         super.setUp();
109 
110         assumeTrue(supportsMultiDisplay());
111         assumeTrue(supportsSystemDecorsOnSecondaryDisplays());
112     }
113 
114     // Wallpaper related tests
115     /**
116      * Test WallpaperService.Engine#getDisplayContext can work on secondary display.
117      */
118     @Test
testWallpaperGetDisplayContext()119     public void testWallpaperGetDisplayContext() throws Exception {
120         assumeTrue(supportsLiveWallpaper());
121 
122         final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession();
123         final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
124 
125         TestJournalContainer.start();
126 
127         final DisplayContent newDisplay = virtualDisplaySession
128                 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
129 
130         wallpaperSession.setWallpaperComponent(TEST_LIVE_WALLPAPER_SERVICE);
131         final String TARGET_ENGINE_DISPLAY_ID = ENGINE_DISPLAY_ID + newDisplay.mId;
132         final TestJournalProvider.TestJournal journal = TestJournalContainer.get(COMPONENT);
133         TestUtils.waitUntil("Waiting for wallpaper engine bounded", 5 /* timeoutSecond */,
134                 () -> journal.extras.getBoolean(TARGET_ENGINE_DISPLAY_ID));
135     }
136 
137     /**
138      * Tests that wallpaper shows on secondary displays.
139      */
140     @Test
testWallpaperShowOnSecondaryDisplays()141     public void testWallpaperShowOnSecondaryDisplays()  {
142         final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession();
143 
144         final DisplayContent untrustedDisplay = createManagedExternalDisplaySession()
145                 .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
146 
147         final DisplayContent decoredSystemDisplay = createManagedVirtualDisplaySession()
148                 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
149 
150         final Bitmap tmpWallpaper = wallpaperSession.getTestBitmap();
151         wallpaperSession.setImageWallpaper(tmpWallpaper);
152 
153         assertTrue("Wallpaper must be displayed on system owned display with system decor flag",
154                 mWmState.waitForWithAmState(
155                         state -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId),
156                         "wallpaper window to show"));
157 
158         assertFalse("Wallpaper must not be displayed on the untrusted display",
159                 isWallpaperOnDisplay(mWmState, untrustedDisplay.mId));
160     }
161 
createManagedChangeWallpaperSession()162     private ChangeWallpaperSession createManagedChangeWallpaperSession() {
163         return mObjectTracker.manage(new ChangeWallpaperSession());
164     }
165 
166     private class ChangeWallpaperSession implements AutoCloseable {
167         private final WallpaperManager mWallpaperManager;
168         private Bitmap mTestBitmap;
169 
ChangeWallpaperSession()170         public ChangeWallpaperSession() {
171             mWallpaperManager = WallpaperManager.getInstance(mContext);
172         }
173 
getTestBitmap()174         public Bitmap getTestBitmap() {
175             if (mTestBitmap == null) {
176                 mTestBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
177                 final Canvas canvas = new Canvas(mTestBitmap);
178                 canvas.drawColor(Color.BLUE);
179             }
180             return mTestBitmap;
181         }
182 
setImageWallpaper(Bitmap bitmap)183         public void setImageWallpaper(Bitmap bitmap) {
184             SystemUtil.runWithShellPermissionIdentity(() ->
185                     mWallpaperManager.setBitmap(bitmap));
186         }
187 
setWallpaperComponent(ComponentName componentName)188         public void setWallpaperComponent(ComponentName componentName) {
189             SystemUtil.runWithShellPermissionIdentity(() ->
190                     mWallpaperManager.setWallpaperComponent(componentName));
191         }
192 
193         @Override
close()194         public void close() {
195             SystemUtil.runWithShellPermissionIdentity(() -> mWallpaperManager.clearWallpaper());
196             if (mTestBitmap != null) {
197                 mTestBitmap.recycle();
198             }
199             // Turning screen off/on to flush deferred color events due to wallpaper changed.
200             pressSleepButton();
201             pressWakeupButton();
202             pressUnlockButton();
203         }
204     }
205 
isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId)206     private boolean isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId) {
207         return windowManagerState.getMatchingWindowType(TYPE_WALLPAPER).stream().anyMatch(
208                 w -> w.getDisplayId() == displayId);
209     }
210 
211     // Navigation bar related tests
212     // TODO(115978725): add runtime sys decor change test once we can do this.
213     /**
214      * Test that navigation bar should show on display with system decoration.
215      */
216     @Test
testNavBarShowingOnDisplayWithDecor()217     public void testNavBarShowingOnDisplayWithDecor() {
218         assumeHasBars();
219         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
220                 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
221 
222         mWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId);
223     }
224 
225     /**
226      * Test that navigation bar should not show on display without system decoration.
227      */
228     @Test
testNavBarNotShowingOnDisplayWithoutDecor()229     public void testNavBarNotShowingOnDisplayWithoutDecor() {
230         assumeHasBars();
231         // Wait for system decoration showing and record current nav states.
232         mWmState.waitForHomeActivityVisible();
233         final List<WindowState> expected = mWmState.getAllNavigationBarStates();
234 
235         createManagedVirtualDisplaySession().setSimulateDisplay(true)
236                 .setShowSystemDecorations(false).createDisplay();
237 
238         waitAndAssertNavBarStatesAreTheSame(expected);
239     }
240 
241     /**
242      * Test that navigation bar should not show on private display even if the display
243      * supports system decoration.
244      */
245     @Test
testNavBarNotShowingOnPrivateDisplay()246     public void testNavBarNotShowingOnPrivateDisplay() {
247         assumeHasBars();
248         // Wait for system decoration showing and record current nav states.
249         mWmState.waitForHomeActivityVisible();
250         final List<WindowState> expected = mWmState.getAllNavigationBarStates();
251 
252         createManagedExternalDisplaySession().setPublicDisplay(false)
253                 .setShowSystemDecorations(true).createVirtualDisplay();
254 
255         waitAndAssertNavBarStatesAreTheSame(expected);
256     }
257 
waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected)258     private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) {
259         // This is used to verify that we have nav bars shown on the same displays
260         // as before the test.
261         //
262         // The strategy is:
263         // Once a display with system ui decor support is created and a nav bar shows on the
264         // display, go back to verify whether the nav bar states are unchanged to verify that no nav
265         // bars were added to a display that was added before executing this method that shouldn't
266         // have nav bars (i.e. private or without system ui decor).
267         try (final VirtualDisplaySession secondDisplaySession = new VirtualDisplaySession()) {
268             final DisplayContent supportsSysDecorDisplay = secondDisplaySession
269                     .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
270             mWmState.waitAndAssertNavBarShownOnDisplay(supportsSysDecorDisplay.mId);
271             // This display has finished his task. Just close it.
272         }
273 
274         mWmState.computeState();
275         final List<WindowState> result = mWmState.getAllNavigationBarStates();
276 
277         assertEquals("The number of nav bars should be the same", expected.size(), result.size());
278 
279         // Nav bars should show on the same displays
280         for (int i = 0; i < expected.size(); i++) {
281             final int expectedDisplayId = expected.get(i).getDisplayId();
282             mWmState.waitAndAssertNavBarShownOnDisplay(expectedDisplayId);
283         }
284     }
285 
286     // Secondary Home related tests
287     /**
288      * Tests launching a home activity on virtual display without system decoration support.
289      */
290     @Test
testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations()291     public void testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations() {
292         createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
293 
294         // Create new virtual display without system decoration support.
295         final DisplayContent newDisplay = createManagedExternalDisplaySession()
296                 .createVirtualDisplay();
297 
298         // Secondary home activity can't be launched on the display without system decoration
299         // support.
300         assertEquals("No stacks on newly launched virtual display", 0, newDisplay.mRootTasks.size());
301     }
302 
303     /** Tests launching a home activity on untrusted virtual display. */
304     @Test
testLaunchHomeActivityOnUntrustedVirtualSecondaryDisplay()305     public void testLaunchHomeActivityOnUntrustedVirtualSecondaryDisplay() {
306         createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
307 
308         // Create new virtual display with system decoration support flag.
309         final DisplayContent newDisplay = createManagedExternalDisplaySession()
310                 .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
311 
312         // Secondary home activity can't be launched on the untrusted virtual display.
313         assertEquals("No stacks on untrusted virtual display", 0, newDisplay.mRootTasks.size());
314     }
315 
316     /**
317      * Tests launching a single instance home activity on virtual display with system decoration
318      * support.
319      */
320     @Test
testLaunchSingleHomeActivityOnDisplayWithDecorations()321     public void testLaunchSingleHomeActivityOnDisplayWithDecorations() {
322         createManagedHomeActivitySession(SINGLE_HOME_ACTIVITY);
323 
324         // If default home doesn't support multi-instance, default secondary home activity
325         // should be automatically launched on the new display.
326         assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent());
327     }
328 
329     /**
330      * Tests launching a single instance home activity with SECONDARY_HOME on virtual display with
331      * system decoration support.
332      */
333     @Test
testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations()334     public void testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations() {
335         createManagedHomeActivitySession(SINGLE_SECONDARY_HOME_ACTIVITY);
336 
337         // If provided secondary home doesn't support multi-instance, default secondary home
338         // activity should be automatically launched on the new display.
339         assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent());
340     }
341 
342     /**
343      * Tests launching a multi-instance home activity on virtual display with system decoration
344      * support.
345      */
346     @Test
testLaunchHomeActivityOnDisplayWithDecorations()347     public void testLaunchHomeActivityOnDisplayWithDecorations() {
348         createManagedHomeActivitySession(HOME_ACTIVITY);
349 
350         // If default home doesn't have SECONDARY_HOME category, default secondary home
351         // activity should be automatically launched on the new display.
352         assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent());
353     }
354 
355     /**
356      * Tests launching a multi-instance home activity with SECONDARY_HOME on virtual display with
357      * system decoration support.
358      */
359     @Test
testLaunchSecondaryHomeActivityOnDisplayWithDecorations()360     public void testLaunchSecondaryHomeActivityOnDisplayWithDecorations() {
361         createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
362 
363         // Provided secondary home activity should be automatically launched on the new display.
364         assertSecondaryHomeResumedOnNewDisplay(SECONDARY_HOME_ACTIVITY);
365     }
366 
assertSecondaryHomeResumedOnNewDisplay(ComponentName homeComponentName)367     private void assertSecondaryHomeResumedOnNewDisplay(ComponentName homeComponentName) {
368         // Create new simulated display with system decoration support.
369         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
370                 .setSimulateDisplay(true)
371                 .setShowSystemDecorations(true)
372                 .createDisplay();
373 
374         waitAndAssertActivityStateOnDisplay(homeComponentName, STATE_RESUMED,
375                 newDisplay.mId, "Activity launched on secondary display must be resumed");
376 
377         tapOnDisplayCenter(newDisplay.mId);
378         assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
379                 mWmState.getFrontStackActivityType(newDisplay.mId));
380     }
381 
382     // IME related tests
383     @Test
testImeWindowCanSwitchToDifferentDisplays()384     public void testImeWindowCanSwitchToDifferentDisplays() throws Exception {
385         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
386 
387         final MockImeSession mockImeSession = createManagedMockImeSession(this);
388         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
389                 createManagedTestActivitySession();
390         final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
391                 createManagedTestActivitySession();
392 
393         // Create a virtual display and launch an activity on it.
394         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
395                 .setShowSystemDecorations(true)
396                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
397                 .setSimulateDisplay(true)
398                 .createDisplay();
399         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
400                 newDisplay.mId);
401 
402         // Make the activity to show soft input.
403         final ImeEventStream stream = mockImeSession.openEventStream();
404         imeTestActivitySession.runOnMainSyncAndWait(
405                 imeTestActivitySession.getActivity()::showSoftInput);
406         waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
407                 editorMatcher("onStartInput",
408                         imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
409                 event -> "showSoftInput".equals(event.getEventName()));
410 
411         // Assert the configuration of the IME window is the same as the configuration of the
412         // virtual display.
413         assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), newDisplay);
414 
415         // Launch another activity on the default display.
416         imeTestActivitySession2.launchTestActivityOnDisplaySync(
417                 ImeTestActivity2.class, DEFAULT_DISPLAY);
418 
419         // Make the activity to show soft input.
420         imeTestActivitySession2.runOnMainSyncAndWait(
421                 imeTestActivitySession2.getActivity()::showSoftInput);
422         waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
423                 editorMatcher("onStartInput",
424                         imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
425                 event -> "showSoftInput".equals(event.getEventName()));
426 
427         // Assert the configuration of the IME window is the same as the configuration of the
428         // default display.
429         assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(),
430                 mWmState.getDisplay(DEFAULT_DISPLAY));
431     }
432 
433     @Test
testImeApiForBug118341760()434     public void testImeApiForBug118341760() throws Exception {
435         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
436 
437         final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5);
438 
439         final MockImeSession mockImeSession = createManagedMockImeSession(this);
440         final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> imeTestActivitySession =
441                 createManagedTestActivitySession();
442         // Create a virtual display and launch an activity on it.
443         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
444                 .setShowSystemDecorations(true)
445                 .setSimulateDisplay(true)
446                 .createDisplay();
447         imeTestActivitySession.launchTestActivityOnDisplaySync(
448                 ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId);
449 
450         final ImeTestActivityWithBrokenContextWrapper activity =
451                 imeTestActivitySession.getActivity();
452         final ImeEventStream stream = mockImeSession.openEventStream();
453         final String privateImeOption = activity.getEditText().getPrivateImeOptions();
454         expectEvent(stream, event -> {
455             if (!TextUtils.equals("onStartInput", event.getEventName())) {
456                 return false;
457             }
458             final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
459             return TextUtils.equals(editorInfo.packageName, mContext.getPackageName())
460                     && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption);
461         }, TIMEOUT_START_INPUT);
462 
463         imeTestActivitySession.runOnMainSyncAndWait(() -> {
464             final InputMethodManager imm = activity.getSystemService(InputMethodManager.class);
465             assertTrue("InputMethodManager.isActive() should work",
466                     imm.isActive(activity.getEditText()));
467         });
468     }
469 
470     @Test
testImeWindowCanSwitchWhenTopFocusedDisplayChange()471     public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception {
472         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
473         // the Activity in the different display.
474         assumeFalse(perDisplayFocusEnabled());
475         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
476 
477         final MockImeSession mockImeSession = createManagedMockImeSession(this);
478         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
479                 createManagedTestActivitySession();
480         final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
481                 createManagedTestActivitySession();
482 
483         // Create a virtual display and launch an activity on virtual & default display.
484         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
485                 .setShowSystemDecorations(true)
486                 .setSimulateDisplay(true)
487                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
488                 .createDisplay();
489         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
490                 DEFAULT_DISPLAY);
491         imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
492                 newDisplay.mId);
493 
494         final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY);
495         final ImeEventStream stream = mockImeSession.openEventStream();
496 
497         // Tap default display as top focused display & request focus on EditText to show
498         // soft input.
499         tapOnDisplayCenter(defDisplay.mId);
500         imeTestActivitySession.runOnMainSyncAndWait(
501                 imeTestActivitySession.getActivity()::showSoftInput);
502         waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.mId,
503                 editorMatcher("onStartInput",
504                         imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
505                 event -> "showSoftInput".equals(event.getEventName()));
506 
507         // Tap virtual display as top focused display & request focus on EditText to show
508         // soft input.
509         tapOnDisplayCenter(newDisplay.mId);
510         imeTestActivitySession2.runOnMainSyncAndWait(
511                 imeTestActivitySession2.getActivity()::showSoftInput);
512         waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
513                 editorMatcher("onStartInput",
514                         imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
515                 event -> "showSoftInput".equals(event.getEventName()));
516 
517         // Tap default display again to make sure the IME window will come back.
518         tapOnDisplayCenter(defDisplay.mId);
519         imeTestActivitySession.runOnMainSyncAndWait(
520                 imeTestActivitySession.getActivity()::showSoftInput);
521         waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.mId,
522                 editorMatcher("onStartInput",
523                         imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
524                 event -> "showSoftInput".equals(event.getEventName()));
525     }
526 
527     /**
528      * Test that the IME can be shown in a different display (actually the default display) than
529      * the display on which the target IME application is shown.  Then test several basic operations
530      * in {@link InputConnection}.
531      */
532     @Test
testCrossDisplayBasicImeOperations()533     public void testCrossDisplayBasicImeOperations() throws Exception {
534         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
535 
536         final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
537 
538         final MockImeSession mockImeSession = createManagedMockImeSession(this);
539         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
540                 createManagedTestActivitySession();
541 
542         // Create a virtual display by app and assume the display should not show IME window.
543         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
544                 .setPublicDisplay(true)
545                 .createDisplay();
546         SystemUtil.runWithShellPermissionIdentity(
547                 () -> assertTrue("Display should not support showing IME window",
548                         mTargetContext.getSystemService(WindowManager.class)
549                                 .getDisplayImePolicy(newDisplay.mId)
550                                 == DISPLAY_IME_POLICY_FALLBACK_DISPLAY));
551 
552         // Launch Ime test activity in virtual display.
553         imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
554                 newDisplay.mId);
555 
556         // Expect onStartInput / showSoftInput would be executed when user tapping on the
557         // non-system created display intentionally.
558         final int[] location = new int[2];
559         imeTestActivitySession.getActivity().mEditText.getLocationOnScreen(location);
560         tapOnDisplaySync(location[0], location[1], newDisplay.mId);
561 
562         // Verify the activity to show soft input on the default display.
563         final ImeEventStream stream = mockImeSession.openEventStream();
564         final EditText editText = imeTestActivitySession.getActivity().mEditText;
565         imeTestActivitySession.runOnMainSyncAndWait(
566                 imeTestActivitySession.getActivity()::showSoftInput);
567         waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
568                 editorMatcher("onStartInput", editText.getPrivateImeOptions()),
569                 event -> "showSoftInput".equals(event.getEventName()));
570 
571         // Commit text & make sure the input texts should be delivered to focused EditText on
572         // virtual display.
573         final String commitText = "test commit";
574         expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT);
575         imeTestActivitySession.runOnMainAndAssertWithTimeout(
576                 () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT,
577                 "The input text should be delivered");
578 
579         // Since the IME and the IME target app are running in different displays,
580         // InputConnection#requestCursorUpdates() is not supported and it should return false.
581         // See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario.
582         final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates(
583                 InputConnection.CURSOR_UPDATE_IMMEDIATE);
584         assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue());
585     }
586 
587     /**
588      * Test that the IME can be hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag.
589      */
590     @Test
testDisplayPolicyImeHideImeOperation()591     public void testDisplayPolicyImeHideImeOperation() throws Exception {
592         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
593 
594         final MockImeSession mockImeSession = createManagedMockImeSession(this);
595         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
596                 createManagedTestActivitySession();
597 
598         // Create a virtual display and launch an activity on virtual display.
599         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
600                 .setShowSystemDecorations(true)
601                 .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
602                 .setSimulateDisplay(true)
603                 .createDisplay();
604 
605         // Launch Ime test activity in virtual display.
606         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
607                 newDisplay.mId);
608 
609         // Verify the activity is launched to the secondary display.
610         final ComponentName imeTestActivityName =
611                 imeTestActivitySession.getActivity().getComponentName();
612         assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue();
613 
614         // Expect onStartInput to not execute when user taps on the display with the HIDE policy.
615         final int[] location = new int[2];
616         imeTestActivitySession.getActivity().mEditText.getLocationOnScreen(location);
617         tapOnDisplaySync(location[0], location[1], newDisplay.mId);
618 
619         // Verify tapping secondary display to request focus on EditText does not show soft input.
620         final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
621         final ImeEventStream stream = mockImeSession.openEventStream();
622         imeTestActivitySession.runOnMainSyncAndWait(
623                 imeTestActivitySession.getActivity()::showSoftInput);
624         notExpectEvent(stream, editorMatcher("onStartInput",
625                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
626                 NOT_EXPECT_TIMEOUT);
627     }
628 
629     /**
630      * Test that the IME remains hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag
631      * if the user taps the EditText on displays with no system decorations.
632      */
633     @Test
testDisplayPolicyImeHideImeNoSystemDecorations()634     public void testDisplayPolicyImeHideImeNoSystemDecorations() throws Exception {
635         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
636 
637         final MockImeSession mockImeSession = createManagedMockImeSession(this);
638 
639         // Launch Ime test activity on default display.
640         final TestActivitySession<ImeTestActivity2> defaultDisplaySession =
641                 createManagedTestActivitySession();
642         defaultDisplaySession.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
643                 DEFAULT_DISPLAY);
644 
645         // Tap the EditText to start IME session.
646         final int[] location = new int[2];
647         EditText editText = defaultDisplaySession.getActivity().mEditText;
648         tapOnDisplayCenter(DEFAULT_DISPLAY);
649         editText.getLocationOnScreen(location);
650         tapOnDisplaySync(location[0], location[1], DEFAULT_DISPLAY);
651 
652         // Verify the activity shows soft input on the default display.
653         final ImeEventStream stream = mockImeSession.openEventStream();
654         waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
655                 editorMatcher("onStartInput", editText.getPrivateImeOptions()),
656                 event -> "showSoftInput".equals(event.getEventName()));
657 
658         // Create a virtual display with the policy to hide the IME.
659         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
660                 .setShowSystemDecorations(false)
661                 .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
662                 .setSimulateDisplay(true)
663                 .createDisplay();
664 
665         SystemUtil.runWithShellPermissionIdentity(
666                 () -> assertTrue("Display should not support showing IME window",
667                         mTargetContext.getSystemService(WindowManager.class)
668                                 .getDisplayImePolicy(newDisplay.mId)
669                                 == DISPLAY_IME_POLICY_HIDE));
670 
671         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
672                 createManagedTestActivitySession();
673 
674         // Launch Ime test activity in virtual display.
675         imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
676                 newDisplay.mId);
677 
678         // Tap the EditText on the virtual display.
679         editText = imeTestActivitySession.getActivity().mEditText;
680         tapOnDisplayCenter(newDisplay.mId);
681         editText.getLocationOnScreen(location);
682         tapOnDisplaySync(location[0], location[1], newDisplay.mId);
683 
684         final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
685 
686         // Verify the activity does not show soft input.
687         notExpectEvent(stream, editorMatcher("onStartInput", editText.getPrivateImeOptions()),
688                 TIMEOUT);
689         InputMethodVisibilityVerifier.expectImeInvisible(TIMEOUT);
690     }
691 
692     @Test
testImeWindowCanShownWhenActivityMovedToDisplay()693     public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
694         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
695         // the Activity in the different display.
696         assumeFalse(perDisplayFocusEnabled());
697         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
698 
699         // Launch a regular activity on default display at the test beginning to prevent the test
700         // may mis-touch the launcher icon that breaks the test expectation.
701         final TestActivitySession<Activities.RegularActivity> testActivitySession =
702                 createManagedTestActivitySession();
703         testActivitySession.launchTestActivityOnDisplaySync(Activities.RegularActivity.class,
704                 DEFAULT_DISPLAY);
705 
706         // Create a virtual display and launch an activity on virtual display.
707         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
708                 .setShowSystemDecorations(true)
709                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
710                 .setSimulateDisplay(true)
711                 .createDisplay();
712 
713         // Leverage MockImeSession to ensure at least an IME exists as default.
714         final MockImeSession mockImeSession = createManagedMockImeSession(this);
715         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
716                 createManagedTestActivitySession();
717         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
718                 newDisplay.mId);
719 
720         // Verify the activity is launched to the secondary display.
721         final ComponentName imeTestActivityName =
722                 imeTestActivitySession.getActivity().getComponentName();
723         assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue();
724 
725         // Tap default display, assume a pointer-out-side event will happened to change the top
726         // display.
727         final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY);
728         tapOnDisplayCenter(defDisplay.mId);
729         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
730         mWmState.assertValidity();
731 
732         // Reparent ImeTestActivity from virtual display to default display.
733         getLaunchActivityBuilder()
734                 .setUseInstrumentation()
735                 .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
736                 .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
737                 .allowMultipleInstances(false)
738                 .setDisplayId(DEFAULT_DISPLAY).execute();
739         waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
740                 DEFAULT_DISPLAY, "Activity launched on default display and on top");
741 
742         // Activity is no longer on the secondary display
743         assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isFalse();
744 
745         // Verify if tapping default display to request focus on EditText can show soft input.
746         final ImeEventStream stream = mockImeSession.openEventStream();
747         tapOnDisplayCenter(defDisplay.mId);
748         imeTestActivitySession.runOnMainSyncAndWait(
749                 imeTestActivitySession.getActivity()::showSoftInput);
750         waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.mId,
751                 editorMatcher("onStartInput",
752                         imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
753                 event -> "showSoftInput".equals(event.getEventName()));
754     }
755 
756     public static class ImeTestActivity extends Activity {
757         ImeAwareEditText mEditText;
758 
759         @Override
onCreate(Bundle icicle)760         protected void onCreate(Bundle icicle) {
761             super.onCreate(icicle);
762             mEditText = new ImeAwareEditText(this);
763             // Set private IME option for editorMatcher to identify which TextView received
764             // onStartInput event.
765             resetPrivateImeOptionsIdentifier();
766             final LinearLayout layout = new LinearLayout(this);
767             layout.setOrientation(LinearLayout.VERTICAL);
768             layout.addView(mEditText);
769             mEditText.requestFocus();
770             setContentView(layout);
771         }
772 
showSoftInput()773         void showSoftInput() {
774             mEditText.scheduleShowSoftInput();
775         }
776 
resetPrivateImeOptionsIdentifier()777         void resetPrivateImeOptionsIdentifier() {
778             mEditText.setPrivateImeOptions(
779                     getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
780         }
781     }
782 
783     public static class ImeTestActivity2 extends ImeTestActivity { }
784 
785     public static final class ImeTestActivityWithBrokenContextWrapper extends Activity {
786         private EditText mEditText;
787 
788         /**
789          * Emulates the behavior of certain {@link ContextWrapper} subclasses we found in the wild.
790          *
791          * <p> Certain {@link ContextWrapper} subclass in the wild delegate method calls to
792          * ApplicationContext except for {@link #getSystemService(String)}.</p>
793          *
794          **/
795         private static final class Bug118341760ContextWrapper extends ContextWrapper {
796             private final Context mOriginalContext;
797 
Bug118341760ContextWrapper(Context base)798             Bug118341760ContextWrapper(Context base) {
799                 super(base.getApplicationContext());
800                 mOriginalContext = base;
801             }
802 
803             /**
804              * Emulates the behavior of {@link ContextWrapper#getSystemService(String)} of certain
805              * {@link ContextWrapper} subclasses we found in the wild.
806              *
807              * @param name The name of the desired service.
808              * @return The service or {@link null} if the name does not exist.
809              */
810             @Override
getSystemService(String name)811             public Object getSystemService(String name) {
812                 return mOriginalContext.getSystemService(name);
813             }
814         }
815 
816         @Override
onCreate(Bundle icicle)817         protected void onCreate(Bundle icicle) {
818             super.onCreate(icicle);
819             mEditText = new EditText(new Bug118341760ContextWrapper(this));
820             // Use SystemClock.elapsedRealtimeNanos()) as a unique ID of this edit text.
821             mEditText.setPrivateImeOptions(Long.toString(SystemClock.elapsedRealtimeNanos()));
822             final LinearLayout layout = new LinearLayout(this);
823             layout.setOrientation(LinearLayout.VERTICAL);
824             layout.addView(mEditText);
825             mEditText.requestFocus();
826             setContentView(layout);
827         }
828 
getEditText()829         EditText getEditText() {
830             return mEditText;
831         }
832     }
833 
assertImeWindowAndDisplayConfiguration( WindowState imeWinState, DisplayContent display)834     private void assertImeWindowAndDisplayConfiguration(
835             WindowState imeWinState, DisplayContent display) {
836         final Configuration configurationForIme = imeWinState.mMergedOverrideConfiguration;
837         final Configuration configurationForDisplay =  display.mMergedOverrideConfiguration;
838         final int displayDensityDpiForIme = configurationForIme.densityDpi;
839         final int displayDensityDpi = configurationForDisplay.densityDpi;
840         final Rect displayBoundsForIme = configurationForIme.windowConfiguration.getBounds();
841         final Rect displayBounds = configurationForDisplay.windowConfiguration.getBounds();
842 
843         assertEquals("Display density not the same", displayDensityDpi, displayDensityDpiForIme);
844         assertEquals("Display bounds not the same", displayBounds, displayBoundsForIme);
845     }
846 }
847