1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.server.wm;
18 
19 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
21 import static android.server.wm.ComponentNameUtils.getActivityName;
22 import static android.server.wm.StateLogger.log;
23 import static android.server.wm.StateLogger.logAlways;
24 import static android.server.wm.UiDeviceUtils.pressSleepButton;
25 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
26 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
27 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
28 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_DESTROY_DISPLAY;
29 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_RESIZE_DISPLAY;
30 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD;
31 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COMMAND;
32 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COUNT;
33 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_DENSITY_DPI;
34 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_LAUNCH_TARGET_COMPONENT;
35 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PRESENTATION_DISPLAY;
36 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY;
37 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY;
38 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_SHOW_SYSTEM_DECORATIONS;
39 import static android.server.wm.app.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
40 import static android.view.Display.DEFAULT_DISPLAY;
41 import static android.view.Display.INVALID_DISPLAY;
42 
43 import static androidx.test.InstrumentationRegistry.getInstrumentation;
44 
45 import static org.hamcrest.MatcherAssert.assertThat;
46 import static org.hamcrest.Matchers.hasSize;
47 import static org.junit.Assert.assertEquals;
48 import static org.junit.Assert.assertTrue;
49 import static org.junit.Assert.fail;
50 
51 import android.content.ComponentName;
52 import android.content.Context;
53 import android.content.res.Configuration;
54 import android.graphics.Rect;
55 import android.os.Bundle;
56 import android.os.SystemClock;
57 import android.provider.Settings;
58 import android.server.wm.ActivityManagerState.ActivityDisplay;
59 import android.server.wm.CommandSession.ActivitySession;
60 import android.server.wm.CommandSession.ActivitySessionClient;
61 import android.server.wm.settings.SettingsSession;
62 import android.util.Size;
63 import android.util.SparseBooleanArray;
64 import android.view.WindowManager;
65 
66 import androidx.annotation.NonNull;
67 import androidx.annotation.Nullable;
68 
69 import com.android.compatibility.common.util.SystemUtil;
70 import com.android.compatibility.common.util.TestUtils;
71 import org.junit.Before;
72 
73 import java.util.ArrayList;
74 import java.util.Collections;
75 import java.util.List;
76 import java.util.function.Consumer;
77 import java.util.function.Predicate;
78 import java.util.regex.Matcher;
79 import java.util.regex.Pattern;
80 
81 /**
82  * Base class for ActivityManager display tests.
83  *
84  * @see DisplayTests
85  * @see MultiDisplayKeyguardTests
86  * @see MultiDisplayLockedKeyguardTests
87  */
88 public class MultiDisplayTestBase extends ActivityManagerTestBase {
89 
90     static final int CUSTOM_DENSITY_DPI = 222;
91     private static final int INVALID_DENSITY_DPI = -1;
92     protected Context mTargetContext;
93 
94     @Before
95     @Override
setUp()96     public void setUp() throws Exception {
97         super.setUp();
98         mTargetContext = getInstrumentation().getTargetContext();
99     }
100 
getDisplayState(int displayId)101     ActivityDisplay getDisplayState(int displayId) {
102         return getDisplayState(getDisplaysStates(), displayId);
103     }
104 
getDisplayState(List<ActivityDisplay> displays, int displayId)105     ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int displayId) {
106         for (ActivityDisplay display : displays) {
107             if (display.mId == displayId) {
108                 return display;
109             }
110         }
111         return null;
112     }
113 
114     /** Return the display state with width, height, dpi. Always not default display. */
getDisplayState(List<ActivityDisplay> displays, int width, int height, int dpi)115     ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int width, int height,
116             int dpi) {
117         for (ActivityDisplay display : displays) {
118             if (display.mId == DEFAULT_DISPLAY) {
119                 continue;
120             }
121             final Configuration config = display.mFullConfiguration;
122             if (config.densityDpi == dpi && config.screenWidthDp == width
123                     && config.screenHeightDp == height) {
124                 return display;
125             }
126         }
127         return null;
128     }
129 
getDisplaysStates()130     List<ActivityDisplay> getDisplaysStates() {
131         mAmWmState.getAmState().computeState();
132         return mAmWmState.getAmState().getDisplays();
133     }
134 
135     /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
findNewDisplayStates(List<ActivityDisplay> oldDisplays, List<ActivityDisplay> newDisplays)136     List<ActivityDisplay> findNewDisplayStates(List<ActivityDisplay> oldDisplays,
137             List<ActivityDisplay> newDisplays) {
138         final ArrayList<ActivityDisplay> result = new ArrayList<>();
139 
140         for (ActivityDisplay newDisplay : newDisplays) {
141             if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
142                 result.add(newDisplay);
143             }
144         }
145 
146         return result;
147     }
148 
149     public static class ReportedDisplayMetrics {
150         private static final String WM_SIZE = "wm size";
151         private static final String WM_DENSITY = "wm density";
152         private static final Pattern PHYSICAL_SIZE =
153                 Pattern.compile("Physical size: (\\d+)x(\\d+)");
154         private static final Pattern OVERRIDE_SIZE =
155                 Pattern.compile("Override size: (\\d+)x(\\d+)");
156         private static final Pattern PHYSICAL_DENSITY =
157                 Pattern.compile("Physical density: (\\d+)");
158         private static final Pattern OVERRIDE_DENSITY =
159                 Pattern.compile("Override density: (\\d+)");
160 
161         @NonNull
162         final Size physicalSize;
163         final int physicalDensity;
164 
165         @Nullable
166         final Size overrideSize;
167         @Nullable
168         final Integer overrideDensity;
169 
170         /** Get physical and override display metrics from WM for specified display. */
getDisplayMetrics(int displayId)171         public static ReportedDisplayMetrics getDisplayMetrics(int displayId) {
172             return new ReportedDisplayMetrics(executeShellCommand(WM_SIZE + " -d " + displayId)
173                             + executeShellCommand(WM_DENSITY + " -d " + displayId));
174         }
175 
setDisplayMetrics(final Size size, final int density)176         void setDisplayMetrics(final Size size, final int density) {
177             setSize(size);
178             setDensity(density);
179         }
180 
restoreDisplayMetrics()181         void restoreDisplayMetrics() {
182             if (overrideSize != null) {
183                 setSize(overrideSize);
184             } else {
185                 executeShellCommand(WM_SIZE + " reset");
186             }
187             if (overrideDensity != null) {
188                 setDensity(overrideDensity);
189             } else {
190                 executeShellCommand(WM_DENSITY + " reset");
191             }
192         }
193 
setSize(final Size size)194         private void setSize(final Size size) {
195             executeShellCommand(WM_SIZE + " " + size.getWidth() + "x" + size.getHeight());
196         }
197 
setDensity(final int density)198         private void setDensity(final int density) {
199             executeShellCommand(WM_DENSITY + " " + density);
200         }
201 
202         /** Get display size that WM operates with. */
getSize()203         public Size getSize() {
204             return overrideSize != null ? overrideSize : physicalSize;
205         }
206 
207         /** Get density that WM operates with. */
getDensity()208         int getDensity() {
209             return overrideDensity != null ? overrideDensity : physicalDensity;
210         }
211 
ReportedDisplayMetrics(final String lines)212         private ReportedDisplayMetrics(final String lines) {
213             Matcher matcher = PHYSICAL_SIZE.matcher(lines);
214             assertTrue("Physical display size must be reported", matcher.find());
215             log(matcher.group());
216             physicalSize = new Size(
217                     Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
218 
219             matcher = PHYSICAL_DENSITY.matcher(lines);
220             assertTrue("Physical display density must be reported", matcher.find());
221             log(matcher.group());
222             physicalDensity = Integer.parseInt(matcher.group(1));
223 
224             matcher = OVERRIDE_SIZE.matcher(lines);
225             if (matcher.find()) {
226                 log(matcher.group());
227                 overrideSize = new Size(
228                         Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
229             } else {
230                 overrideSize = null;
231             }
232 
233             matcher = OVERRIDE_DENSITY.matcher(lines);
234             if (matcher.find()) {
235                 log(matcher.group());
236                 overrideDensity = Integer.parseInt(matcher.group(1));
237             } else {
238                 overrideDensity = null;
239             }
240         }
241     }
242 
tapOnDisplayCenter(int displayId)243     protected void tapOnDisplayCenter(int displayId) {
244         final Rect bounds = mAmWmState.getWmState().getDisplay(displayId).getDisplayRect();
245         tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId);
246     }
247 
waitForDisplayGone(Predicate<WindowManagerState.Display> displayPredicate)248     private void waitForDisplayGone(Predicate<WindowManagerState.Display> displayPredicate) {
249         for (int retry = 1; retry <= 5; retry++) {
250             mAmWmState.computeState(true);
251             if (!mAmWmState.getWmState().getDisplays().stream().anyMatch(displayPredicate::test)) {
252                 return;
253             }
254             logAlways("Waiting for hosted displays destruction... retry=" + retry);
255             SystemClock.sleep(500);
256         }
257         fail("Waiting for hosted displays destruction failed.");
258     }
259 
260     /**
261      * This class should only be used when you need to test virtual display created by a
262      * non-privileged app.
263      * Or when you need to test on simulated display.
264      *
265      * If you need to test virtual display created by a privileged app, please use
266      * {@link ExternalDisplaySession} instead.
267      */
268     public class VirtualDisplaySession implements AutoCloseable {
269         private int mDensityDpi = CUSTOM_DENSITY_DPI;
270         private boolean mLaunchInSplitScreen = false;
271         private boolean mCanShowWithInsecureKeyguard = false;
272         private boolean mPublicDisplay = false;
273         private boolean mResizeDisplay = true;
274         private boolean mShowSystemDecorations = false;
275         private boolean mPresentationDisplay = false;
276         private ComponentName mLaunchActivity = null;
277         private boolean mSimulateDisplay = false;
278         private boolean mMustBeCreated = true;
279         private Size mSimulationDisplaySize = new Size(1024 /* width */, 768 /* height */);
280 
281         private boolean mVirtualDisplayCreated = false;
282         private final OverlayDisplayDevicesSession mOverlayDisplayDeviceSession =
283                 new OverlayDisplayDevicesSession(mContext);
284 
setDensityDpi(int densityDpi)285         VirtualDisplaySession setDensityDpi(int densityDpi) {
286             mDensityDpi = densityDpi;
287             return this;
288         }
289 
setLaunchInSplitScreen(boolean launchInSplitScreen)290         VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) {
291             mLaunchInSplitScreen = launchInSplitScreen;
292             return this;
293         }
294 
setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)295         VirtualDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) {
296             mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
297             return this;
298         }
299 
setPublicDisplay(boolean publicDisplay)300         VirtualDisplaySession setPublicDisplay(boolean publicDisplay) {
301             mPublicDisplay = publicDisplay;
302             return this;
303         }
304 
setResizeDisplay(boolean resizeDisplay)305         VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) {
306             mResizeDisplay = resizeDisplay;
307             return this;
308         }
309 
setShowSystemDecorations(boolean showSystemDecorations)310         VirtualDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
311             mShowSystemDecorations = showSystemDecorations;
312             return this;
313         }
314 
setPresentationDisplay(boolean presentationDisplay)315         VirtualDisplaySession setPresentationDisplay(boolean presentationDisplay) {
316             mPresentationDisplay = presentationDisplay;
317             return this;
318         }
319 
setLaunchActivity(ComponentName launchActivity)320         VirtualDisplaySession setLaunchActivity(ComponentName launchActivity) {
321             mLaunchActivity = launchActivity;
322             return this;
323         }
324 
setSimulateDisplay(boolean simulateDisplay)325         public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
326             mSimulateDisplay = simulateDisplay;
327             return this;
328         }
329 
setSimulationDisplaySize(int width, int height)330         VirtualDisplaySession setSimulationDisplaySize(int width, int height) {
331             mSimulationDisplaySize = new Size(width, height);
332             return this;
333         }
334 
setMustBeCreated(boolean mustBeCreated)335         VirtualDisplaySession setMustBeCreated(boolean mustBeCreated) {
336             mMustBeCreated = mustBeCreated;
337             return this;
338         }
339 
340         @Nullable
createDisplay()341         public ActivityDisplay createDisplay() throws Exception {
342             return createDisplays(1).stream().findFirst().orElse(null);
343         }
344 
345         @NonNull
createDisplays(int count)346         List<ActivityDisplay> createDisplays(int count) throws Exception {
347             if (mSimulateDisplay) {
348                 return simulateDisplay();
349             } else {
350                 return createVirtualDisplays(count);
351             }
352         }
353 
resizeDisplay()354         void resizeDisplay() {
355             executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
356                     + " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
357         }
358 
359         @Override
close()360         public void close() throws Exception {
361             mOverlayDisplayDeviceSession.close();
362             if (mVirtualDisplayCreated) {
363                 destroyVirtualDisplays();
364                 mVirtualDisplayCreated = false;
365             }
366         }
367 
368         /**
369          * Simulate new display.
370          * <pre>
371          * <code>mDensityDpi</code> provide custom density for the display.
372          * </pre>
373          * @return {@link ActivityDisplay} of newly created display.
374          */
simulateDisplay()375         private List<ActivityDisplay> simulateDisplay() throws Exception {
376             // Create virtual display with custom density dpi and specified size.
377             mOverlayDisplayDeviceSession.set(mSimulationDisplaySize + "/" + mDensityDpi);
378             if (mShowSystemDecorations) {
379                 mOverlayDisplayDeviceSession.configureDisplays(
380                         true /* requestShowSysDecors */, true /* requestShowIme */);
381             }
382             return mOverlayDisplayDeviceSession.getCreatedDisplays();
383         }
384 
385         /**
386          * Create new virtual display.
387          * <pre>
388          * <code>mDensityDpi</code> provide custom density for the display.
389          * <code>mLaunchInSplitScreen</code> start
390          *     {@link android.server.wm.app.VirtualDisplayActivity} to side from
391          *     {@link android.server.wm.app.LaunchingActivity} on primary display.
392          * <code>mCanShowWithInsecureKeyguard</code>  allow showing content when device is
393          *     showing an insecure keyguard.
394          * <code>mMustBeCreated</code> should assert if the display was or wasn't created.
395          * <code>mPublicDisplay</code> make display public.
396          * <code>mResizeDisplay</code> should resize display when surface size changes.
397          * <code>LaunchActivity</code> should launch test activity immediately after display
398          *     creation.
399          * </pre>
400          * @param displayCount number of displays to be created.
401          * @return A list of {@link ActivityDisplay} that represent newly created displays.
402          * @throws Exception
403          */
createVirtualDisplays(int displayCount)404         private List<ActivityDisplay> createVirtualDisplays(int displayCount) {
405             // Start an activity that is able to create virtual displays.
406             if (mLaunchInSplitScreen) {
407                 getLaunchActivityBuilder()
408                         .setToSide(true)
409                         .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
410                         .execute();
411             } else {
412                 launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
413             }
414             mAmWmState.computeState(false /* compareTaskAndStackBounds */,
415                     new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
416             mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
417             mAmWmState.assertFocusedActivity("Focus must be on virtual display host activity",
418                     VIRTUAL_DISPLAY_ACTIVITY);
419             final List<ActivityDisplay> originalDS = getDisplaysStates();
420 
421             // Create virtual display with custom density dpi.
422             final StringBuilder createVirtualDisplayCommand = new StringBuilder(
423                     getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY))
424                     .append(" -f 0x20000000")
425                     .append(" --es " + KEY_COMMAND + " " + COMMAND_CREATE_DISPLAY);
426             if (mDensityDpi != INVALID_DENSITY_DPI) {
427                 createVirtualDisplayCommand
428                         .append(" --ei " + KEY_DENSITY_DPI + " ")
429                         .append(mDensityDpi);
430             }
431             createVirtualDisplayCommand.append(" --ei " + KEY_COUNT + " ").append(displayCount)
432                     .append(" --ez " + KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD + " ")
433                     .append(mCanShowWithInsecureKeyguard)
434                     .append(" --ez " + KEY_PUBLIC_DISPLAY + " ").append(mPublicDisplay)
435                     .append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay)
436                     .append(" --ez " + KEY_SHOW_SYSTEM_DECORATIONS + " ")
437                     .append(mShowSystemDecorations)
438                     .append(" --ez " + KEY_PRESENTATION_DISPLAY + " ").append(mPresentationDisplay);
439             if (mLaunchActivity != null) {
440                 createVirtualDisplayCommand
441                         .append(" --es " + KEY_LAUNCH_TARGET_COMPONENT + " ")
442                         .append(getActivityName(mLaunchActivity));
443             }
444             executeShellCommand(createVirtualDisplayCommand.toString());
445             mVirtualDisplayCreated = true;
446 
447             return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS);
448         }
449 
450         /**
451          * Destroy existing virtual display.
452          */
destroyVirtualDisplays()453         void destroyVirtualDisplays() {
454             final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
455                     + " -f 0x20000000"
456                     + " --es " + KEY_COMMAND + " " + COMMAND_DESTROY_DISPLAY;
457             executeShellCommand(destroyVirtualDisplayCommand);
458             waitForDisplayGone(
459                     d -> d.getName() != null && d.getName().contains(VIRTUAL_DISPLAY_PREFIX));
460         }
461     }
462 
463     // TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated.
464     protected class VirtualDisplayLauncher extends VirtualDisplaySession {
465         private final ActivitySessionClient mActivitySessionClient = new ActivitySessionClient();
466 
launchActivityOnDisplay(ComponentName activityName, ActivityDisplay display)467         ActivitySession launchActivityOnDisplay(ComponentName activityName,
468                 ActivityDisplay display) {
469             return launchActivityOnDisplay(activityName, display, null /* extrasConsumer */,
470                     true /* withShellPermission */, true /* waitForLaunch */);
471         }
472 
launchActivityOnDisplay(ComponentName activityName, ActivityDisplay display, Consumer<Bundle> extrasConsumer, boolean withShellPermission, boolean waitForLaunch)473         ActivitySession launchActivityOnDisplay(ComponentName activityName,
474                 ActivityDisplay display, Consumer<Bundle> extrasConsumer,
475                 boolean withShellPermission, boolean waitForLaunch) {
476             return launchActivity(builder -> builder
477                     // VirtualDisplayActivity is in different package. If the display is not public,
478                     // it requires shell permission to launch activity ({@see com.android.server.wm.
479                     // ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay}).
480                     .setWithShellPermission(withShellPermission)
481                     .setWaitForLaunched(waitForLaunch)
482                     .setIntentExtra(extrasConsumer)
483                     .setTargetActivity(activityName)
484                     .setDisplayId(display.mId));
485         }
486 
launchActivity(Consumer<LaunchActivityBuilder> setupBuilder)487         ActivitySession launchActivity(Consumer<LaunchActivityBuilder> setupBuilder) {
488             final LaunchActivityBuilder builder = getLaunchActivityBuilder()
489                     .setUseInstrumentation();
490             setupBuilder.accept(builder);
491             return mActivitySessionClient.startActivity(builder);
492         }
493 
494         @Override
close()495         public void close() throws Exception {
496             super.close();
497             mActivitySessionClient.close();
498         }
499     }
500 
501     /** Helper class to save, set, and restore overlay_display_devices preference. */
502     private class OverlayDisplayDevicesSession extends SettingsSession<String> {
503         /** The displays which are created by this session. */
504         private final List<ActivityDisplay> mDisplays = new ArrayList<>();
505         /** The configured displays that need to be restored when this session is closed. */
506         private final List<OverlayDisplayState> mDisplayStates = new ArrayList<>();
507         private final WindowManager mWm;
508 
OverlayDisplayDevicesSession(Context context)509         OverlayDisplayDevicesSession(Context context) {
510             super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
511                     Settings.Global::getString,
512                     Settings.Global::putString);
513             mWm = context.getSystemService(WindowManager.class);
514         }
515 
getCreatedDisplays()516         List<ActivityDisplay> getCreatedDisplays() {
517             return new ArrayList<>(mDisplays);
518         }
519 
520         @Override
set(String value)521         public void set(String value) {
522             final List<ActivityDisplay> originalDisplays = getDisplaysStates();
523             super.set(value);
524             final int newDisplayCount = 1 + (int) value.chars().filter(ch -> ch == ';').count();
525             mDisplays.addAll(assertAndGetNewDisplays(newDisplayCount, originalDisplays));
526         }
527 
configureDisplays(boolean requestShowSysDecors, boolean requestShowIme)528         void configureDisplays(boolean requestShowSysDecors, boolean requestShowIme) {
529             SystemUtil.runWithShellPermissionIdentity(() -> {
530                 for (ActivityDisplay display : mDisplays) {
531                     final boolean showSystemDecors = mWm.shouldShowSystemDecors(display.mId);
532                     final boolean showIme = mWm.shouldShowIme(display.mId);
533                     mDisplayStates.add(new OverlayDisplayState(
534                             display.mId, showSystemDecors, showIme));
535                     if (requestShowSysDecors != showSystemDecors) {
536                         mWm.setShouldShowSystemDecors(display.mId, requestShowSysDecors);
537                         TestUtils.waitUntil("Waiting for display show system decors",
538                                 5 /* timeoutSecond */,
539                                 () -> mWm.shouldShowSystemDecors(
540                                         display.mId) == requestShowSysDecors);
541                     }
542                     if (requestShowIme != showIme) {
543                         mWm.setShouldShowIme(display.mId, requestShowIme);
544                         TestUtils.waitUntil("Waiting for display show Ime",
545                                 5 /* timeoutSecond */,
546                                 () -> mWm.shouldShowIme(display.mId) == requestShowIme);
547                     }
548                 }
549             });
550         }
551 
restoreDisplayStates()552         private void restoreDisplayStates() {
553             mDisplayStates.forEach(state -> SystemUtil.runWithShellPermissionIdentity(() -> {
554                 mWm.setShouldShowSystemDecors(state.mId, state.mShouldShowSystemDecors);
555                 mWm.setShouldShowIme(state.mId, state.mShouldShowIme);
556 
557                 // Only need to wait the last flag to be set.
558                 TestUtils.waitUntil("Waiting for the show IME flag to be set",
559                         5 /* timeoutSecond */,
560                         () -> mWm.shouldShowIme(state.mId) == state.mShouldShowIme);
561             }));
562         }
563 
564         @Override
close()565         public void close() throws Exception {
566             // Need to restore display state before display is destroyed.
567             restoreDisplayStates();
568             super.close();
569             waitForDisplayGone(display -> mDisplays.stream()
570                     .anyMatch(state -> state.mId == display.getDisplayId()));
571         }
572 
573         private class OverlayDisplayState {
574             int mId;
575             boolean mShouldShowSystemDecors;
576             boolean mShouldShowIme;
577 
OverlayDisplayState(int displayId, boolean showSysDecors, boolean showIme)578             OverlayDisplayState(int displayId, boolean showSysDecors, boolean showIme) {
579                 mId = displayId;
580                 mShouldShowSystemDecors = showSysDecors;
581                 mShouldShowIme = showIme;
582             }
583         }
584     }
585 
586     /** Wait for provided number of displays and report their configurations. */
getDisplayStateAfterChange(int expectedDisplayCount)587     List<ActivityDisplay> getDisplayStateAfterChange(int expectedDisplayCount) {
588         List<ActivityDisplay> ds = getDisplaysStates();
589 
590         int retriesLeft = 5;
591         while (!areDisplaysValid(ds, expectedDisplayCount) && retriesLeft-- > 0) {
592             log("***Waiting for the correct number of displays...");
593             try {
594                 Thread.sleep(1000);
595             } catch (InterruptedException e) {
596                 log(e.toString());
597             }
598             ds = getDisplaysStates();
599         }
600 
601         return ds;
602     }
603 
areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount)604     private boolean areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount) {
605         if (displays.size() != expectedDisplayCount) {
606             return false;
607         }
608         for (ActivityDisplay display : displays) {
609             if (display.mOverrideConfiguration.densityDpi == 0) {
610                 return false;
611             }
612         }
613         return true;
614     }
615 
616     /**
617      * Wait for desired number of displays to be created and get their properties.
618      *
619      * @param newDisplayCount expected display count, -1 if display should not be created.
620      * @param originalDisplays display states before creation of new display(s).
621      * @return list of new displays, empty list if no new display is created.
622      */
assertAndGetNewDisplays(int newDisplayCount, List<ActivityDisplay> originalDisplays)623     private List<ActivityDisplay> assertAndGetNewDisplays(int newDisplayCount,
624             List<ActivityDisplay> originalDisplays) {
625         final int originalDisplayCount = originalDisplays.size();
626 
627         // Wait for the display(s) to be created and get configurations.
628         final List<ActivityDisplay> ds = getDisplayStateAfterChange(
629                 originalDisplayCount + newDisplayCount);
630         if (newDisplayCount != -1) {
631             assertEquals("New virtual display(s) must be created",
632                     originalDisplayCount + newDisplayCount, ds.size());
633         } else {
634             assertEquals("New virtual display must not be created",
635                     originalDisplayCount, ds.size());
636             return Collections.emptyList();
637         }
638 
639         // Find the newly added display(s).
640         final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDisplays, ds);
641         assertThat("New virtual display must be created", newDisplays, hasSize(newDisplayCount));
642 
643         return newDisplays;
644     }
645 
646     /** Checks if the device supports multi-display. */
supportsMultiDisplay()647     protected boolean supportsMultiDisplay() {
648         return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
649     }
650 
651     /**
652      * This class is used when you need to test virtual display created by a privileged app.
653      *
654      * If you need to test virtual display created by a non-privileged app or when you need to test
655      * on simulated display, please use {@link VirtualDisplaySession} instead.
656      */
657     public class ExternalDisplaySession implements AutoCloseable {
658 
659         private boolean mCanShowWithInsecureKeyguard = false;
660         private boolean mPublicDisplay = false;
661         private boolean mShowSystemDecorations = false;
662 
663         private int mDisplayId = INVALID_DISPLAY;
664 
665         @Nullable
666         private VirtualDisplayHelper mExternalDisplayHelper;
667 
setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)668         ExternalDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) {
669             mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
670             return this;
671         }
672 
setPublicDisplay(boolean publicDisplay)673         ExternalDisplaySession setPublicDisplay(boolean publicDisplay) {
674             mPublicDisplay = publicDisplay;
675             return this;
676         }
677 
setShowSystemDecorations(boolean showSystemDecorations)678         ExternalDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
679             mShowSystemDecorations = showSystemDecorations;
680             return this;
681         }
682 
683         /**
684          * Creates a private virtual display with insecure keyguard flags set.
685          */
createVirtualDisplay()686         ActivityDisplay createVirtualDisplay() throws Exception {
687             final List<ActivityDisplay> originalDS = getDisplaysStates();
688             final int originalDisplayCount = originalDS.size();
689 
690             mExternalDisplayHelper = new VirtualDisplayHelper();
691             mExternalDisplayHelper
692                     .setPublicDisplay(mPublicDisplay)
693                     .setCanShowWithInsecureKeyguard(mCanShowWithInsecureKeyguard)
694                     .setShowSystemDecorations(mShowSystemDecorations)
695                     .createAndWaitForDisplay();
696 
697             // Wait for the virtual display to be created and get configurations.
698             final List<ActivityDisplay> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
699             assertEquals("New virtual display must be created", originalDisplayCount + 1,
700                     ds.size());
701 
702             // Find the newly added display.
703             final ActivityDisplay newDisplay = findNewDisplayStates(originalDS, ds).get(0);
704             mDisplayId = newDisplay.mId;
705             return newDisplay;
706         }
707 
turnDisplayOff()708         void turnDisplayOff() {
709             if (mExternalDisplayHelper == null) {
710                 throw new RuntimeException("No external display created");
711             }
712             mExternalDisplayHelper.turnDisplayOff();
713         }
714 
turnDisplayOn()715         void turnDisplayOn() {
716             if (mExternalDisplayHelper == null) {
717                 throw new RuntimeException("No external display created");
718             }
719             mExternalDisplayHelper.turnDisplayOn();
720         }
721 
722         @Override
close()723         public void close() throws Exception {
724             if (mExternalDisplayHelper != null) {
725                 mExternalDisplayHelper.releaseDisplay();
726                 mExternalDisplayHelper = null;
727 
728                 waitForDisplayGone(d -> d.getDisplayId() == mDisplayId);
729                 mDisplayId = INVALID_DISPLAY;
730             }
731         }
732     }
733 
734     public static class PrimaryDisplayStateSession implements AutoCloseable {
735 
turnScreenOff()736         void turnScreenOff() {
737             setPrimaryDisplayState(false);
738         }
739 
740         @Override
close()741         public void close() throws Exception {
742             setPrimaryDisplayState(true);
743         }
744 
745         /** Turns the primary display on/off by pressing the power key */
setPrimaryDisplayState(boolean wantOn)746         private void setPrimaryDisplayState(boolean wantOn) {
747             if (wantOn) {
748                 pressWakeupButton();
749             } else {
750                 pressSleepButton();
751             }
752             VirtualDisplayHelper.waitForDefaultDisplayState(wantOn);
753         }
754     }
755 }
756