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