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