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.cts;
18 
19 import com.android.tradefed.device.CollectingOutputReceiver;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 
22 import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
23 import static android.server.cts.StateLogger.log;
24 import static android.server.cts.StateLogger.logE;
25 
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 
35 /**
36  * Base class for ActivityManager display tests.
37  *
38  * @see ActivityManagerDisplayTests
39  * @see ActivityManagerDisplayLockedKeyguardTests
40  */
41 public class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
42 
43     static final int CUSTOM_DENSITY_DPI = 222;
44 
45     private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity processes";
46     private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
47     private static final int INVALID_DENSITY_DPI = -1;
48 
49     private boolean mVirtualDisplayCreated;
50 
51     /** Temp storage used for parsing. */
52     final LinkedList<String> mDumpLines = new LinkedList<>();
53 
54     @Override
tearDown()55     protected void tearDown() throws Exception {
56         try {
57             destroyVirtualDisplays();
58         } catch (DeviceNotAvailableException e) {
59             logE(e.getMessage());
60         }
61         super.tearDown();
62     }
63 
64     /** Contains the configurations applied to attached displays. */
65     static final class DisplayState {
66         int mDisplayId;
67         String mOverrideConfig;
68 
DisplayState(int displayId, String overrideConfig)69         private DisplayState(int displayId, String overrideConfig) {
70             mDisplayId = displayId;
71             mOverrideConfig = overrideConfig;
72         }
73 
getWidth()74         private int getWidth() {
75             final String[] configParts = mOverrideConfig.split(" ");
76             for (String part : configParts) {
77                 if (part.endsWith("dp") && part.startsWith("w")) {
78                     final String widthString = part.substring(1, part.length() - 3);
79                     return Integer.parseInt(widthString);
80                 }
81             }
82 
83             return -1;
84         }
85 
getHeight()86         private int getHeight() {
87             final String[] configParts = mOverrideConfig.split(" ");
88             for (String part : configParts) {
89                 if (part.endsWith("dp") && part.startsWith("h")) {
90                     final String heightString = part.substring(1, part.length() - 3);
91                     return Integer.parseInt(heightString);
92                 }
93             }
94 
95             return -1;
96         }
97 
getDpi()98         int getDpi() {
99             final String[] configParts = mOverrideConfig.split(" ");
100             for (String part : configParts) {
101                 if (part.endsWith("dpi")) {
102                     final String densityDpiString = part.substring(0, part.length() - 3);
103                     return Integer.parseInt(densityDpiString);
104                 }
105             }
106 
107             return -1;
108         }
109     }
110 
111     /** Contains the configurations applied to attached displays. */
112     static final class ReportedDisplays {
113         private static final Pattern sGlobalConfigurationPattern =
114                 Pattern.compile("mGlobalConfiguration: (\\{.*\\})");
115         private static final Pattern sDisplayOverrideConfigurationsPattern =
116                 Pattern.compile("Display override configurations:");
117         private static final Pattern sDisplayConfigPattern =
118                 Pattern.compile("(\\d+): (\\{.*\\})");
119 
120         String mGlobalConfig;
121         private Map<Integer, DisplayState> mDisplayStates = new HashMap<>();
122 
create(LinkedList<String> dump)123         static ReportedDisplays create(LinkedList<String> dump) {
124             final ReportedDisplays result = new ReportedDisplays();
125 
126             while (!dump.isEmpty()) {
127                 final String line = dump.pop().trim();
128 
129                 Matcher matcher = sDisplayOverrideConfigurationsPattern.matcher(line);
130                 if (matcher.matches()) {
131                     log(line);
132                     while (ReportedDisplays.shouldContinueExtracting(dump, sDisplayConfigPattern)) {
133                         final String displayOverrideConfigLine = dump.pop().trim();
134                         log(displayOverrideConfigLine);
135                         matcher = sDisplayConfigPattern.matcher(displayOverrideConfigLine);
136                         matcher.matches();
137                         final Integer displayId = Integer.valueOf(matcher.group(1));
138                         result.mDisplayStates.put(displayId,
139                                 new DisplayState(displayId, matcher.group(2)));
140                     }
141                     continue;
142                 }
143 
144                 matcher = sGlobalConfigurationPattern.matcher(line);
145                 if (matcher.matches()) {
146                     log(line);
147                     result.mGlobalConfig = matcher.group(1);
148                 }
149             }
150 
151             return result;
152         }
153 
154         /** Check if next line in dump matches the pattern and we should continue extracting. */
shouldContinueExtracting(LinkedList<String> dump, Pattern matchingPattern)155         static boolean shouldContinueExtracting(LinkedList<String> dump, Pattern matchingPattern) {
156             if (dump.isEmpty()) {
157                 return false;
158             }
159 
160             final String line = dump.peek().trim();
161             return matchingPattern.matcher(line).matches();
162         }
163 
getDisplayState(int displayId)164         DisplayState getDisplayState(int displayId) {
165             return mDisplayStates.get(displayId);
166         }
167 
168         /** Return the display state with width, height, dpi */
getDisplayState(int width, int height, int dpi)169         DisplayState getDisplayState(int width, int height, int dpi) {
170             for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
171                 final DisplayState ds = entry.getValue();
172                 if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == dpi
173                         && ds.getWidth() == width && ds.getHeight() == height) {
174                     return ds;
175                 }
176             }
177             return null;
178         }
179 
180         /** Check if reported state is valid. */
isValidState(int expectedDisplayCount)181         boolean isValidState(int expectedDisplayCount) {
182             if (mDisplayStates.size() != expectedDisplayCount) {
183                 return false;
184             }
185 
186             for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
187                 final DisplayState ds = entry.getValue();
188                 if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == -1) {
189                     return false;
190                 }
191             }
192             return true;
193         }
194     }
195 
getDisplaysStates()196     ReportedDisplays getDisplaysStates() throws DeviceNotAvailableException {
197         final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
198         mDevice.executeShellCommand(DUMPSYS_ACTIVITY_PROCESSES, outputReceiver);
199         String dump = outputReceiver.getOutput();
200         mDumpLines.clear();
201 
202         Collections.addAll(mDumpLines, dump.split("\\n"));
203 
204         return ReportedDisplays.create(mDumpLines);
205     }
206 
207     /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
findNewDisplayStates( ReportedDisplays oldDisplays, ReportedDisplays newDisplays)208     private List<ActivityManagerDisplayTests.DisplayState> findNewDisplayStates(
209             ReportedDisplays oldDisplays, ReportedDisplays newDisplays) {
210         final ArrayList<DisplayState> displays = new ArrayList();
211 
212         for (Integer displayId : newDisplays.mDisplayStates.keySet()) {
213             if (!oldDisplays.mDisplayStates.containsKey(displayId)) {
214                 displays.add(newDisplays.getDisplayState(displayId));
215             }
216         }
217 
218         return displays;
219     }
220 
221     /**
222      * Create new virtual display.
223      * @param densityDpi provide custom density for the display.
224      * @param launchInSplitScreen start {@link VirtualDisplayActivity} to side from
225      *                            {@link LaunchingActivity} on primary display.
226      * @param publicDisplay make display public.
227      * @param resizeDisplay should resize display when surface size changes.
228      * @param launchActivity should launch test activity immediately after display creation.
229      * @return {@link ActivityManagerDisplayTests.DisplayState} of newly created display.
230      * @throws Exception
231      */
createVirtualDisplays(int densityDpi, boolean launchInSplitScreen, boolean publicDisplay, boolean resizeDisplay, String launchActivity, int displayCount)232     private List<ActivityManagerDisplayTests.DisplayState> createVirtualDisplays(int densityDpi,
233             boolean launchInSplitScreen, boolean publicDisplay, boolean resizeDisplay,
234             String launchActivity, int displayCount) throws Exception {
235         // Start an activity that is able to create virtual displays.
236         if (launchInSplitScreen) {
237             getLaunchActivityBuilder().setToSide(true)
238                     .setTargetActivityName(VIRTUAL_DISPLAY_ACTIVITY).execute();
239         } else {
240             launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
241         }
242         mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY},
243                 false /* compareTaskAndStackBounds */);
244         final ActivityManagerDisplayTests.ReportedDisplays originalDS = getDisplaysStates();
245         final int originalDisplayCount = originalDS.mDisplayStates.size();
246 
247         // Create virtual display with custom density dpi.
248         executeShellCommand(getCreateVirtualDisplayCommand(densityDpi, publicDisplay, resizeDisplay,
249                 launchActivity, displayCount));
250         mVirtualDisplayCreated = true;
251 
252         // Wait for the virtual display to be created and get configurations.
253         final ActivityManagerDisplayTests.ReportedDisplays ds =
254                 getDisplayStateAfterChange(originalDisplayCount + displayCount);
255         assertEquals("New virtual display must be created",
256                 originalDisplayCount + displayCount, ds.mDisplayStates.size());
257 
258         // Find the newly added display.
259         final List<ActivityManagerDisplayTests.DisplayState> newDisplays
260                 = findNewDisplayStates(originalDS, ds);
261         assertTrue("New virtual display must be created", displayCount == newDisplays.size());
262 
263         return newDisplays;
264     }
265 
266     /**
267      * Destroy existing virtual display.
268      */
destroyVirtualDisplays()269     void destroyVirtualDisplays() throws Exception {
270         if (mVirtualDisplayCreated) {
271             executeShellCommand(getDestroyVirtualDisplayCommand());
272             mVirtualDisplayCreated = false;
273         }
274     }
275 
276     static class VirtualDisplayBuilder {
277         private final ActivityManagerDisplayTestBase mTests;
278 
279         private int mDensityDpi = CUSTOM_DENSITY_DPI;
280         private boolean mLaunchInSplitScreen = false;
281         private boolean mPublicDisplay = false;
282         private boolean mResizeDisplay = true;
283         private String mLaunchActivity = null;
284 
VirtualDisplayBuilder(ActivityManagerDisplayTestBase tests)285         public VirtualDisplayBuilder(ActivityManagerDisplayTestBase tests) {
286             mTests = tests;
287         }
288 
setDensityDpi(int densityDpi)289         public VirtualDisplayBuilder setDensityDpi(int densityDpi) {
290             mDensityDpi = densityDpi;
291             return this;
292         }
293 
setLaunchInSplitScreen(boolean launchInSplitScreen)294         public VirtualDisplayBuilder setLaunchInSplitScreen(boolean launchInSplitScreen) {
295             mLaunchInSplitScreen = launchInSplitScreen;
296             return this;
297         }
298 
setPublicDisplay(boolean publicDisplay)299         public VirtualDisplayBuilder setPublicDisplay(boolean publicDisplay) {
300             mPublicDisplay = publicDisplay;
301             return this;
302         }
303 
setResizeDisplay(boolean resizeDisplay)304         public VirtualDisplayBuilder setResizeDisplay(boolean resizeDisplay) {
305             mResizeDisplay = resizeDisplay;
306             return this;
307         }
308 
setLaunchActivity(String launchActivity)309         public VirtualDisplayBuilder setLaunchActivity(String launchActivity) {
310             mLaunchActivity = launchActivity;
311             return this;
312         }
313 
build()314         public DisplayState build() throws Exception {
315             final List<DisplayState> displays = build(1);
316             return displays != null && !displays.isEmpty() ? displays.get(0) : null;
317         }
318 
build(int count)319         public List<DisplayState> build(int count) throws Exception {
320             return mTests.createVirtualDisplays(mDensityDpi, mLaunchInSplitScreen, mPublicDisplay,
321                     mResizeDisplay, mLaunchActivity, count);
322         }
323     }
324 
getCreateVirtualDisplayCommand(int densityDpi, boolean publicDisplay, boolean resizeDisplay, String launchActivity, int displayCount)325     private static String getCreateVirtualDisplayCommand(int densityDpi, boolean publicDisplay,
326             boolean resizeDisplay, String launchActivity, int displayCount) {
327         final StringBuilder commandBuilder
328                 = new StringBuilder(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY));
329         commandBuilder.append(" -f 0x20000000");
330         commandBuilder.append(" --es command create_display");
331         if (densityDpi != INVALID_DENSITY_DPI) {
332             commandBuilder.append(" --ei density_dpi ").append(densityDpi);
333         }
334         commandBuilder.append(" --ei count ").append(displayCount);
335         commandBuilder.append(" --ez public_display ").append(publicDisplay);
336         commandBuilder.append(" --ez resize_display ").append(resizeDisplay);
337         if (launchActivity != null) {
338             commandBuilder.append(" --es launch_target_activity ").append(launchActivity);
339         }
340         return commandBuilder.toString();
341     }
342 
getDestroyVirtualDisplayCommand()343     private static String getDestroyVirtualDisplayCommand() {
344         return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" +
345                 " --es command destroy_display";
346     }
347 
348     /** Wait for provided number of displays and report their configurations. */
getDisplayStateAfterChange(int expectedDisplayCount)349     ReportedDisplays getDisplayStateAfterChange(int expectedDisplayCount)
350             throws DeviceNotAvailableException {
351         ReportedDisplays ds = getDisplaysStates();
352 
353         int retriesLeft = 5;
354         while (!ds.isValidState(expectedDisplayCount) && retriesLeft-- > 0) {
355             log("***Waiting for the correct number of displays...");
356             try {
357                 Thread.sleep(1000);
358             } catch (InterruptedException e) {
359                 log(e.toString());
360             }
361             ds = getDisplaysStates();
362         }
363 
364         return ds;
365     }
366 
367     /** Checks if the device supports multi-display. */
supportsMultiDisplay()368     boolean supportsMultiDisplay() throws Exception {
369         return hasDeviceFeature("android.software.activities_on_secondary_displays");
370     }
371 }
372