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.am;
18 
19 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
20 import static android.server.am.ComponentNameUtils.getActivityName;
21 import static android.server.am.Components.VIRTUAL_DISPLAY_ACTIVITY;
22 import static android.server.am.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
23 import static android.server.am.Components.VirtualDisplayActivity.COMMAND_DESTROY_DISPLAY;
24 import static android.server.am.Components.VirtualDisplayActivity.COMMAND_RESIZE_DISPLAY;
25 import static android.server.am.Components.VirtualDisplayActivity
26         .KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD;
27 import static android.server.am.Components.VirtualDisplayActivity.KEY_COMMAND;
28 import static android.server.am.Components.VirtualDisplayActivity.KEY_COUNT;
29 import static android.server.am.Components.VirtualDisplayActivity.KEY_DENSITY_DPI;
30 import static android.server.am.Components.VirtualDisplayActivity.KEY_LAUNCH_TARGET_COMPONENT;
31 import static android.server.am.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY;
32 import static android.server.am.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY;
33 import static android.server.am.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
34 import static android.server.am.StateLogger.log;
35 import static android.server.am.StateLogger.logAlways;
36 import static android.view.Display.DEFAULT_DISPLAY;
37 
38 import static org.hamcrest.MatcherAssert.assertThat;
39 import static org.hamcrest.Matchers.hasSize;
40 import static org.junit.Assert.assertEquals;
41 import static org.junit.Assert.assertTrue;
42 import static org.junit.Assert.fail;
43 
44 import android.content.ComponentName;
45 import android.content.res.Configuration;
46 import android.os.SystemClock;
47 import android.provider.Settings;
48 import android.server.am.ActivityManagerState.ActivityDisplay;
49 import android.server.am.settings.SettingsSession;
50 import androidx.annotation.NonNull;
51 import androidx.annotation.Nullable;
52 import android.util.Size;
53 
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.List;
57 import java.util.regex.Matcher;
58 import java.util.regex.Pattern;
59 
60 /**
61  * Base class for ActivityManager display tests.
62  *
63  * @see ActivityManagerDisplayTests
64  * @see ActivityManagerDisplayLockedKeyguardTests
65  */
66 class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
67 
68     static final int CUSTOM_DENSITY_DPI = 222;
69     private static final int INVALID_DENSITY_DPI = -1;
70 
getDisplayState(List<ActivityDisplay> displays, int displayId)71     ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int displayId) {
72         for (ActivityDisplay display : displays) {
73             if (display.mId == displayId) {
74                 return display;
75             }
76         }
77         return null;
78     }
79 
80     /** Return the display state with width, height, dpi. Always not default display. */
getDisplayState(List<ActivityDisplay> displays, int width, int height, int dpi)81     ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int width, int height,
82             int dpi) {
83         for (ActivityDisplay display : displays) {
84             if (display.mId == DEFAULT_DISPLAY) {
85                 continue;
86             }
87             final Configuration config = display.mFullConfiguration;
88             if (config.densityDpi == dpi && config.screenWidthDp == width
89                     && config.screenHeightDp == height) {
90                 return display;
91             }
92         }
93         return null;
94     }
95 
getDisplaysStates()96     List<ActivityDisplay> getDisplaysStates() {
97         mAmWmState.getAmState().computeState();
98         return mAmWmState.getAmState().getDisplays();
99     }
100 
101     /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
findNewDisplayStates(List<ActivityDisplay> oldDisplays, List<ActivityDisplay> newDisplays)102     List<ActivityDisplay> findNewDisplayStates(List<ActivityDisplay> oldDisplays,
103             List<ActivityDisplay> newDisplays) {
104         final ArrayList<ActivityDisplay> result = new ArrayList<>();
105 
106         for (ActivityDisplay newDisplay : newDisplays) {
107             if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
108                 result.add(newDisplay);
109             }
110         }
111 
112         return result;
113     }
114 
115     static class ReportedDisplayMetrics {
116         private static final String WM_SIZE = "wm size";
117         private static final String WM_DENSITY = "wm density";
118         private static final Pattern PHYSICAL_SIZE =
119                 Pattern.compile("Physical size: (\\d+)x(\\d+)");
120         private static final Pattern OVERRIDE_SIZE =
121                 Pattern.compile("Override size: (\\d+)x(\\d+)");
122         private static final Pattern PHYSICAL_DENSITY =
123                 Pattern.compile("Physical density: (\\d+)");
124         private static final Pattern OVERRIDE_DENSITY =
125                 Pattern.compile("Override density: (\\d+)");
126 
127         @NonNull
128         final Size physicalSize;
129         final int physicalDensity;
130 
131         @Nullable
132         final Size overrideSize;
133         @Nullable
134         final Integer overrideDensity;
135 
136         /** Get physical and override display metrics from WM. */
getDisplayMetrics()137         static ReportedDisplayMetrics getDisplayMetrics() {
138             return new ReportedDisplayMetrics(
139                     executeShellCommand(WM_SIZE) + executeShellCommand(WM_DENSITY));
140         }
141 
setDisplayMetrics(final Size size, final int density)142         void setDisplayMetrics(final Size size, final int density) {
143             setSize(size);
144             setDensity(density);
145         }
146 
restoreDisplayMetrics()147         void restoreDisplayMetrics() {
148             if (overrideSize != null) {
149                 setSize(overrideSize);
150             } else {
151                 executeShellCommand(WM_SIZE + " reset");
152             }
153             if (overrideDensity != null) {
154                 setDensity(overrideDensity);
155             } else {
156                 executeShellCommand(WM_DENSITY + " reset");
157             }
158         }
159 
setSize(final Size size)160         private void setSize(final Size size) {
161             executeShellCommand(WM_SIZE + " " + size.getWidth() + "x" + size.getHeight());
162         }
163 
setDensity(final int density)164         private void setDensity(final int density) {
165             executeShellCommand(WM_DENSITY + " " + density);
166         }
167 
168         /** Get display size that WM operates with. */
getSize()169         Size getSize() {
170             return overrideSize != null ? overrideSize : physicalSize;
171         }
172 
173         /** Get density that WM operates with. */
getDensity()174         int getDensity() {
175             return overrideDensity != null ? overrideDensity : physicalDensity;
176         }
177 
ReportedDisplayMetrics(final String lines)178         private ReportedDisplayMetrics(final String lines) {
179             Matcher matcher = PHYSICAL_SIZE.matcher(lines);
180             assertTrue("Physical display size must be reported", matcher.find());
181             log(matcher.group());
182             physicalSize = new Size(
183                     Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
184 
185             matcher = PHYSICAL_DENSITY.matcher(lines);
186             assertTrue("Physical display density must be reported", matcher.find());
187             log(matcher.group());
188             physicalDensity = Integer.parseInt(matcher.group(1));
189 
190             matcher = OVERRIDE_SIZE.matcher(lines);
191             if (matcher.find()) {
192                 log(matcher.group());
193                 overrideSize = new Size(
194                         Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
195             } else {
196                 overrideSize = null;
197             }
198 
199             matcher = OVERRIDE_DENSITY.matcher(lines);
200             if (matcher.find()) {
201                 log(matcher.group());
202                 overrideDensity = Integer.parseInt(matcher.group(1));
203             } else {
204                 overrideDensity = null;
205             }
206         }
207     }
208 
209     protected class VirtualDisplaySession implements AutoCloseable {
210         private int mDensityDpi = CUSTOM_DENSITY_DPI;
211         private boolean mLaunchInSplitScreen = false;
212         private boolean mCanShowWithInsecureKeyguard = false;
213         private boolean mPublicDisplay = false;
214         private boolean mResizeDisplay = true;
215         private ComponentName mLaunchActivity = null;
216         private boolean mSimulateDisplay = false;
217         private boolean mMustBeCreated = true;
218 
219         private boolean mVirtualDisplayCreated = false;
220         private final OverlayDisplayDevicesSession mOverlayDisplayDeviceSession =
221                 new OverlayDisplayDevicesSession();
222 
setDensityDpi(int densityDpi)223         VirtualDisplaySession setDensityDpi(int densityDpi) {
224             mDensityDpi = densityDpi;
225             return this;
226         }
227 
setLaunchInSplitScreen(boolean launchInSplitScreen)228         VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) {
229             mLaunchInSplitScreen = launchInSplitScreen;
230             return this;
231         }
232 
setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)233         VirtualDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) {
234             mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
235             return this;
236         }
237 
setPublicDisplay(boolean publicDisplay)238         VirtualDisplaySession setPublicDisplay(boolean publicDisplay) {
239             mPublicDisplay = publicDisplay;
240             return this;
241         }
242 
setResizeDisplay(boolean resizeDisplay)243         VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) {
244             mResizeDisplay = resizeDisplay;
245             return this;
246         }
247 
setLaunchActivity(ComponentName launchActivity)248         VirtualDisplaySession setLaunchActivity(ComponentName launchActivity) {
249             mLaunchActivity = launchActivity;
250             return this;
251         }
252 
setSimulateDisplay(boolean simulateDisplay)253         VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
254             mSimulateDisplay = simulateDisplay;
255             return this;
256         }
257 
setMustBeCreated(boolean mustBeCreated)258         VirtualDisplaySession setMustBeCreated(boolean mustBeCreated) {
259             mMustBeCreated = mustBeCreated;
260             return this;
261         }
262 
263         @Nullable
createDisplay()264         ActivityDisplay createDisplay() throws Exception {
265             return createDisplays(1).stream().findFirst().orElse(null);
266         }
267 
268         @NonNull
createDisplays(int count)269         List<ActivityDisplay> createDisplays(int count) throws Exception {
270             if (mSimulateDisplay) {
271                 return simulateDisplay();
272             } else {
273                 return createVirtualDisplays(count);
274             }
275         }
276 
resizeDisplay()277         void resizeDisplay() {
278             executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
279                     + " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
280         }
281 
282         @Override
close()283         public void close() throws Exception {
284             mOverlayDisplayDeviceSession.close();
285             if (mVirtualDisplayCreated) {
286                 destroyVirtualDisplays();
287                 mVirtualDisplayCreated = false;
288             }
289         }
290 
291         /**
292          * Simulate new display.
293          * <pre>
294          * <code>mDensityDpi</code> provide custom density for the display.
295          * </pre>
296          * @return {@link ActivityDisplay} of newly created display.
297          */
simulateDisplay()298         private List<ActivityDisplay> simulateDisplay() throws Exception {
299             final List<ActivityDisplay> originalDs = getDisplaysStates();
300 
301             // Create virtual display with custom density dpi.
302             mOverlayDisplayDeviceSession.set("1024x768/" + mDensityDpi);
303 
304             return assertAndGetNewDisplays(1, originalDs);
305         }
306 
307         /**
308          * Create new virtual display.
309          * <pre>
310          * <code>mDensityDpi</code> provide custom density for the display.
311          * <code>mLaunchInSplitScreen</code> start {@link VirtualDisplayActivity} to side from
312          *     {@link LaunchingActivity} on primary display.
313          * <code>mCanShowWithInsecureKeyguard</code>  allow showing content when device is
314          *     showing an insecure keyguard.
315          * <code>mMustBeCreated</code> should assert if the display was or wasn't created.
316          * <code>mPublicDisplay</code> make display public.
317          * <code>mResizeDisplay</code> should resize display when surface size changes.
318          * <code>LaunchActivity</code> should launch test activity immediately after display
319          *     creation.
320          * </pre>
321          * @param displayCount number of displays to be created.
322          * @return A list of {@link ActivityDisplay} that represent newly created displays.
323          * @throws Exception
324          */
createVirtualDisplays(int displayCount)325         private List<ActivityDisplay> createVirtualDisplays(int displayCount) {
326             // Start an activity that is able to create virtual displays.
327             if (mLaunchInSplitScreen) {
328                 getLaunchActivityBuilder()
329                         .setToSide(true)
330                         .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
331                         .execute();
332             } else {
333                 launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
334             }
335             mAmWmState.computeState(false /* compareTaskAndStackBounds */,
336                     new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
337             mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
338             mAmWmState.assertFocusedActivity("Focus must be on virtual display host activity",
339                     VIRTUAL_DISPLAY_ACTIVITY);
340             final List<ActivityDisplay> originalDS = getDisplaysStates();
341 
342             // Create virtual display with custom density dpi.
343             final StringBuilder createVirtualDisplayCommand = new StringBuilder(
344                     getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY))
345                     .append(" -f 0x20000000")
346                     .append(" --es " + KEY_COMMAND + " " + COMMAND_CREATE_DISPLAY);
347             if (mDensityDpi != INVALID_DENSITY_DPI) {
348                 createVirtualDisplayCommand
349                         .append(" --ei " + KEY_DENSITY_DPI + " ")
350                         .append(mDensityDpi);
351             }
352             createVirtualDisplayCommand.append(" --ei " + KEY_COUNT + " ").append(displayCount)
353                     .append(" --ez " + KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD + " ")
354                     .append(mCanShowWithInsecureKeyguard)
355                     .append(" --ez " + KEY_PUBLIC_DISPLAY + " ").append(mPublicDisplay)
356                     .append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay);
357             if (mLaunchActivity != null) {
358                 createVirtualDisplayCommand
359                         .append(" --es " + KEY_LAUNCH_TARGET_COMPONENT + " ")
360                         .append(getActivityName(mLaunchActivity));
361             }
362             executeShellCommand(createVirtualDisplayCommand.toString());
363             mVirtualDisplayCreated = true;
364 
365             return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS);
366         }
367 
368         /**
369          * Destroy existing virtual display.
370          */
destroyVirtualDisplays()371         void destroyVirtualDisplays() {
372             final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
373                     + " -f 0x20000000"
374                     + " --es " + KEY_COMMAND + " " + COMMAND_DESTROY_DISPLAY;
375             executeShellCommand(destroyVirtualDisplayCommand);
376             waitForDisplaysDestroyed();
377         }
378 
waitForDisplaysDestroyed()379         private void waitForDisplaysDestroyed() {
380             for (int retry = 1; retry <= 5; retry++) {
381                 if (!isHostedVirtualDisplayPresent()) {
382                     return;
383                 }
384                 logAlways("Waiting for hosted displays destruction... retry=" + retry);
385                 SystemClock.sleep(500);
386             }
387             fail("Waiting for hosted displays destruction failed.");
388         }
389 
isHostedVirtualDisplayPresent()390         private boolean isHostedVirtualDisplayPresent() {
391             mAmWmState.computeState(true);
392             return mAmWmState.getWmState().getDisplays().stream().anyMatch(
393                     d -> d.getName() != null && d.getName().contains(VIRTUAL_DISPLAY_PREFIX));
394         }
395 
396         /**
397          * Wait for desired number of displays to be created and get their properties.
398          * @param newDisplayCount expected display count, -1 if display should not be created.
399          * @param originalDS display states before creation of new display(s).
400          * @return list of new displays, empty list if no new display is created.
401          */
assertAndGetNewDisplays(int newDisplayCount, List<ActivityDisplay> originalDS)402         private List<ActivityDisplay> assertAndGetNewDisplays(int newDisplayCount,
403                 List<ActivityDisplay> originalDS) {
404             final int originalDisplayCount = originalDS.size();
405 
406             // Wait for the display(s) to be created and get configurations.
407             final List<ActivityDisplay> ds = getDisplayStateAfterChange(
408                     originalDisplayCount + newDisplayCount);
409             if (newDisplayCount != -1) {
410                 assertEquals("New virtual display(s) must be created",
411                         originalDisplayCount + newDisplayCount, ds.size());
412             } else {
413                 assertEquals("New virtual display must not be created",
414                         originalDisplayCount, ds.size());
415                 return Collections.emptyList();
416             }
417 
418             // Find the newly added display(s).
419             final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDS, ds);
420             assertThat("New virtual display must be created",
421                     newDisplays, hasSize(newDisplayCount));
422 
423             return newDisplays;
424         }
425     }
426 
427     /** Helper class to save, set, and restore overlay_display_devices preference. */
428     private static class OverlayDisplayDevicesSession extends SettingsSession<String> {
OverlayDisplayDevicesSession()429         OverlayDisplayDevicesSession() {
430             super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
431                     Settings.Global::getString,
432                     Settings.Global::putString);
433         }
434     }
435 
436     /** Wait for provided number of displays and report their configurations. */
getDisplayStateAfterChange(int expectedDisplayCount)437     List<ActivityDisplay> getDisplayStateAfterChange(int expectedDisplayCount) {
438         List<ActivityDisplay> ds = getDisplaysStates();
439 
440         int retriesLeft = 5;
441         while (!areDisplaysValid(ds, expectedDisplayCount) && retriesLeft-- > 0) {
442             log("***Waiting for the correct number of displays...");
443             try {
444                 Thread.sleep(1000);
445             } catch (InterruptedException e) {
446                 log(e.toString());
447             }
448             ds = getDisplaysStates();
449         }
450 
451         return ds;
452     }
453 
areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount)454     private boolean areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount) {
455         if (displays.size() != expectedDisplayCount) {
456             return false;
457         }
458         for (ActivityDisplay display : displays) {
459             if (display.mOverrideConfiguration.densityDpi == 0) {
460                 return false;
461             }
462         }
463         return true;
464     }
465 
466     /** Checks if the device supports multi-display. */
supportsMultiDisplay()467     boolean supportsMultiDisplay() {
468         return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
469     }
470 }
471