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