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.content.res.Configuration.ORIENTATION_LANDSCAPE; 21 import static android.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS; 22 import static android.server.wm.StateLogger.log; 23 import static android.server.wm.UiDeviceUtils.pressSleepButton; 24 import static android.server.wm.UiDeviceUtils.pressWakeupButton; 25 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY; 26 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY; 27 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_DESTROY_DISPLAY; 28 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_RESIZE_DISPLAY; 29 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD; 30 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COMMAND; 31 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COUNT; 32 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_DENSITY_DPI; 33 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PRESENTATION_DISPLAY; 34 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY; 35 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY; 36 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_SHOW_SYSTEM_DECORATIONS; 37 import static android.server.wm.app.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX; 38 import static android.view.Display.DEFAULT_DISPLAY; 39 import static android.view.Display.INVALID_DISPLAY; 40 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 41 42 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 43 44 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 45 46 import static org.hamcrest.MatcherAssert.assertThat; 47 import static org.hamcrest.Matchers.hasSize; 48 import static org.junit.Assert.assertEquals; 49 import static org.junit.Assert.assertNotEquals; 50 import static org.junit.Assert.assertTrue; 51 52 import android.content.ComponentName; 53 import android.content.Context; 54 import android.content.pm.PackageManager; 55 import android.content.res.Configuration; 56 import android.os.Bundle; 57 import android.provider.Settings; 58 import android.server.wm.CommandSession.ActivitySession; 59 import android.server.wm.CommandSession.ActivitySessionClient; 60 import android.server.wm.WindowManagerState.DisplayContent; 61 import android.server.wm.settings.SettingsSession; 62 import android.util.Pair; 63 import android.util.Size; 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.cts.mockime.ImeEvent; 71 import com.android.cts.mockime.ImeEventStream; 72 73 import org.junit.AfterClass; 74 import org.junit.Before; 75 import org.junit.BeforeClass; 76 77 import java.util.ArrayList; 78 import java.util.Collections; 79 import java.util.List; 80 import java.util.Objects; 81 import java.util.concurrent.TimeUnit; 82 import java.util.function.Consumer; 83 import java.util.function.Predicate; 84 import java.util.regex.Matcher; 85 import java.util.regex.Pattern; 86 87 /** 88 * Base class for ActivityManager display tests. 89 * 90 * @see DisplayTests 91 * @see MultiDisplayKeyguardTests 92 * @see MultiDisplayLockedKeyguardTests 93 * @see AppConfigurationTests 94 */ 95 public class MultiDisplayTestBase extends ActivityManagerTestBase { 96 97 private static SettingsSession<String> sImmersiveModeConfirmationSetting; 98 99 static final int CUSTOM_DENSITY_DPI = 222; 100 private static final int INVALID_DENSITY_DPI = -1; 101 protected Context mTargetContext; 102 103 @BeforeClass setUpClass()104 public static void setUpClass() { 105 sImmersiveModeConfirmationSetting = new SettingsSession<>( 106 Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS), 107 Settings.Secure::getString, Settings.Secure::putString); 108 sImmersiveModeConfirmationSetting.set("confirmed"); 109 } 110 111 @AfterClass tearDownClass()112 public static void tearDownClass() { 113 if (sImmersiveModeConfirmationSetting != null) { 114 sImmersiveModeConfirmationSetting.close(); 115 } 116 } 117 118 @Before 119 @Override setUp()120 public void setUp() throws Exception { 121 super.setUp(); 122 mTargetContext = getInstrumentation().getTargetContext(); 123 } 124 getDisplayState(int displayId)125 DisplayContent getDisplayState(int displayId) { 126 return getDisplayState(getDisplaysStates(), displayId); 127 } 128 getDisplayState(List<DisplayContent> displays, int displayId)129 DisplayContent getDisplayState(List<DisplayContent> displays, int displayId) { 130 for (DisplayContent display : displays) { 131 if (display.mId == displayId) { 132 return display; 133 } 134 } 135 return null; 136 } 137 138 /** Return the display state with width, height, dpi. Always not default display. */ getDisplayState(List<DisplayContent> displays, int width, int height, int dpi)139 DisplayContent getDisplayState(List<DisplayContent> displays, int width, int height, 140 int dpi) { 141 for (DisplayContent display : displays) { 142 if (display.mId == DEFAULT_DISPLAY) { 143 continue; 144 } 145 final Configuration config = display.mFullConfiguration; 146 if (config.densityDpi == dpi && config.screenWidthDp == width 147 && config.screenHeightDp == height) { 148 return display; 149 } 150 } 151 return null; 152 } 153 getDisplaysStates()154 List<DisplayContent> getDisplaysStates() { 155 mWmState.computeState(); 156 return mWmState.getDisplays(); 157 } 158 159 /** Find the display that was not originally reported in oldDisplays and added in newDisplays */ findNewDisplayStates(List<DisplayContent> oldDisplays, List<DisplayContent> newDisplays)160 List<DisplayContent> findNewDisplayStates(List<DisplayContent> oldDisplays, 161 List<DisplayContent> newDisplays) { 162 final ArrayList<DisplayContent> result = new ArrayList<>(); 163 164 for (DisplayContent newDisplay : newDisplays) { 165 if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) { 166 result.add(newDisplay); 167 } 168 } 169 170 return result; 171 } 172 173 public static class ReportedDisplayMetrics { 174 private static final String WM_SIZE = "wm size"; 175 private static final String WM_DENSITY = "wm density"; 176 private static final Pattern PHYSICAL_SIZE = 177 Pattern.compile("Physical size: (\\d+)x(\\d+)"); 178 private static final Pattern OVERRIDE_SIZE = 179 Pattern.compile("Override size: (\\d+)x(\\d+)"); 180 private static final Pattern PHYSICAL_DENSITY = 181 Pattern.compile("Physical density: (\\d+)"); 182 private static final Pattern OVERRIDE_DENSITY = 183 Pattern.compile("Override density: (\\d+)"); 184 185 /** The size of the physical display. */ 186 @NonNull 187 final Size physicalSize; 188 /** The density of the physical display. */ 189 final int physicalDensity; 190 191 /** The pre-existing size override applied to a logical display. */ 192 @Nullable 193 final Size overrideSize; 194 /** The pre-existing density override applied to a logical display. */ 195 @Nullable 196 final Integer overrideDensity; 197 198 final int mDisplayId; 199 200 /** Get physical and override display metrics from WM for specified display. */ getDisplayMetrics(int displayId)201 public static ReportedDisplayMetrics getDisplayMetrics(int displayId) { 202 return new ReportedDisplayMetrics(executeShellCommand(WM_SIZE + " -d " + displayId) 203 + executeShellCommand(WM_DENSITY + " -d " + displayId), displayId); 204 } 205 setDisplayMetrics(final Size size, final int density)206 void setDisplayMetrics(final Size size, final int density) { 207 setSize(size); 208 setDensity(density); 209 } 210 restoreDisplayMetrics()211 void restoreDisplayMetrics() { 212 if (overrideSize != null) { 213 setSize(overrideSize); 214 } else { 215 executeShellCommand(WM_SIZE + " reset -d " + mDisplayId); 216 } 217 if (overrideDensity != null) { 218 setDensity(overrideDensity); 219 } else { 220 executeShellCommand(WM_DENSITY + " reset -d " + mDisplayId); 221 } 222 } 223 setSize(final Size size)224 private void setSize(final Size size) { 225 executeShellCommand( 226 WM_SIZE + " " + size.getWidth() + "x" + size.getHeight() + " -d " + mDisplayId); 227 } 228 setDensity(final int density)229 private void setDensity(final int density) { 230 executeShellCommand(WM_DENSITY + " " + density + " -d " + mDisplayId); 231 } 232 233 /** Get display size that WM operates with. */ getSize()234 public Size getSize() { 235 return overrideSize != null ? overrideSize : physicalSize; 236 } 237 238 /** Get density that WM operates with. */ getDensity()239 int getDensity() { 240 return overrideDensity != null ? overrideDensity : physicalDensity; 241 } 242 ReportedDisplayMetrics(final String lines, int displayId)243 private ReportedDisplayMetrics(final String lines, int displayId) { 244 mDisplayId = displayId; 245 Matcher matcher = PHYSICAL_SIZE.matcher(lines); 246 assertTrue("Physical display size must be reported", matcher.find()); 247 log(matcher.group()); 248 physicalSize = new Size( 249 Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2))); 250 251 matcher = PHYSICAL_DENSITY.matcher(lines); 252 assertTrue("Physical display density must be reported", matcher.find()); 253 log(matcher.group()); 254 physicalDensity = Integer.parseInt(matcher.group(1)); 255 256 matcher = OVERRIDE_SIZE.matcher(lines); 257 if (matcher.find()) { 258 log(matcher.group()); 259 overrideSize = new Size( 260 Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2))); 261 } else { 262 overrideSize = null; 263 } 264 265 matcher = OVERRIDE_DENSITY.matcher(lines); 266 if (matcher.find()) { 267 log(matcher.group()); 268 overrideDensity = Integer.parseInt(matcher.group(1)); 269 } else { 270 overrideDensity = null; 271 } 272 } 273 } 274 275 public static class DisplayMetricsSession implements AutoCloseable { 276 private final ReportedDisplayMetrics mInitialDisplayMetrics; 277 private final int mDisplayId; 278 DisplayMetricsSession(int displayId)279 DisplayMetricsSession(int displayId) { 280 mDisplayId = displayId; 281 mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(mDisplayId); 282 } 283 getInitialDisplayMetrics()284 ReportedDisplayMetrics getInitialDisplayMetrics() { 285 return mInitialDisplayMetrics; 286 } 287 getDisplayMetrics()288 ReportedDisplayMetrics getDisplayMetrics() { 289 return ReportedDisplayMetrics.getDisplayMetrics(mDisplayId); 290 } 291 changeAspectRatio(double aspectRatio, int orientation)292 void changeAspectRatio(double aspectRatio, int orientation) { 293 final Size originalSize = mInitialDisplayMetrics.physicalSize; 294 final int smaller = Math.min(originalSize.getWidth(), originalSize.getHeight()); 295 final int larger = (int) (smaller * aspectRatio); 296 Size overrideSize; 297 if (orientation == ORIENTATION_LANDSCAPE) { 298 overrideSize = new Size(larger, smaller); 299 } 300 else { 301 overrideSize = new Size(smaller, larger); 302 } 303 overrideDisplayMetrics(overrideSize, mInitialDisplayMetrics.physicalDensity); 304 } 305 changeDisplayMetrics(double sizeRatio, double densityRatio)306 void changeDisplayMetrics(double sizeRatio, double densityRatio) { 307 // Given a display may already have an override applied before the test is begun, 308 // resize based upon the override. 309 final Size originalSize; 310 final int density; 311 if (mInitialDisplayMetrics.overrideSize != null) { 312 originalSize = mInitialDisplayMetrics.overrideSize; 313 } else { 314 originalSize = mInitialDisplayMetrics.physicalSize; 315 } 316 317 if (mInitialDisplayMetrics.overrideDensity != null) { 318 density = mInitialDisplayMetrics.overrideDensity; 319 } else { 320 density = mInitialDisplayMetrics.physicalDensity; 321 } 322 323 final Size overrideSize = new Size((int)(originalSize.getWidth() * sizeRatio), 324 (int)(originalSize.getHeight() * sizeRatio)); 325 final int overrideDensity = (int)(density * densityRatio); 326 overrideDisplayMetrics(overrideSize, overrideDensity); 327 } 328 overrideDisplayMetrics(final Size size, final int density)329 void overrideDisplayMetrics(final Size size, final int density) { 330 mInitialDisplayMetrics.setDisplayMetrics(size, density); 331 } 332 restoreDisplayMetrics()333 void restoreDisplayMetrics() { 334 mInitialDisplayMetrics.restoreDisplayMetrics(); 335 } 336 337 @Override close()338 public void close() { 339 restoreDisplayMetrics(); 340 } 341 } 342 343 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedDisplayMetricsSession(int displayId)344 protected DisplayMetricsSession createManagedDisplayMetricsSession(int displayId) { 345 return mObjectTracker.manage(new DisplayMetricsSession(displayId)); 346 } 347 348 public static class LetterboxAspectRatioSession implements AutoCloseable { 349 private static final String WM_SET_IGNORE_ORIENTATION_REQUEST = 350 "wm set-ignore-orientation-request "; 351 private static final String WM_GET_IGNORE_ORIENTATION_REQUEST = 352 "wm get-ignore-orientation-request"; 353 private static final Pattern IGNORE_ORIENTATION_REQUEST_PATTERN = 354 Pattern.compile("ignoreOrientationRequest (true|false) for displayId=\\d+"); 355 356 private static final String WM_SET_LETTERBOX_STYLE_ASPECT_RATIO = 357 "wm set-letterbox-style --aspectRatio "; 358 private static final String WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO 359 = "wm reset-letterbox-style aspectRatio"; 360 361 final int mDisplayId; 362 final boolean mInitialIgnoreOrientationRequest; 363 LetterboxAspectRatioSession(int displayId, float aspectRatio)364 LetterboxAspectRatioSession(int displayId, float aspectRatio) { 365 mDisplayId = displayId; 366 Matcher matcher = IGNORE_ORIENTATION_REQUEST_PATTERN.matcher( 367 executeShellCommand(WM_GET_IGNORE_ORIENTATION_REQUEST + " -d " + mDisplayId)); 368 assertTrue("get-ignore-orientation-request should match pattern", matcher.find()); 369 mInitialIgnoreOrientationRequest = Boolean.parseBoolean(matcher.group(1)); 370 371 executeShellCommand("wm set-ignore-orientation-request true -d " + mDisplayId); 372 executeShellCommand(WM_SET_LETTERBOX_STYLE_ASPECT_RATIO + aspectRatio); 373 } 374 375 @Override close()376 public void close() { 377 executeShellCommand( 378 WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialIgnoreOrientationRequest + " -d " 379 + mDisplayId); 380 executeShellCommand(WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO); 381 } 382 } 383 384 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedLetterboxAspectRatioSession(int displayId, float aspectRatio)385 protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession(int displayId, 386 float aspectRatio) { 387 return mObjectTracker.manage(new LetterboxAspectRatioSession(displayId, aspectRatio)); 388 } 389 waitForDisplayGone(Predicate<DisplayContent> displayPredicate)390 void waitForDisplayGone(Predicate<DisplayContent> displayPredicate) { 391 waitForOrFail("displays to be removed", () -> { 392 mWmState.computeState(); 393 return mWmState.getDisplays().stream().noneMatch(displayPredicate); 394 }); 395 } 396 397 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedVirtualDisplaySession()398 protected VirtualDisplaySession createManagedVirtualDisplaySession() { 399 return mObjectTracker.manage(new VirtualDisplaySession()); 400 } 401 402 /** 403 * This class should only be used when you need to test virtual display created by a 404 * non-privileged app. 405 * Or when you need to test on simulated display. 406 * 407 * If you need to test virtual display created by a privileged app, please use 408 * {@link ExternalDisplaySession} instead. 409 */ 410 public class VirtualDisplaySession implements AutoCloseable { 411 private int mDensityDpi = CUSTOM_DENSITY_DPI; 412 private boolean mLaunchInSplitScreen = false; 413 private boolean mCanShowWithInsecureKeyguard = false; 414 private boolean mPublicDisplay = false; 415 private boolean mResizeDisplay = true; 416 private boolean mShowSystemDecorations = false; 417 private boolean mOwnContentOnly = false; 418 private int mDisplayImePolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 419 private boolean mPresentationDisplay = false; 420 private boolean mSimulateDisplay = false; 421 private boolean mMustBeCreated = true; 422 private Size mSimulationDisplaySize = new Size(1024 /* width */, 768 /* height */); 423 424 private boolean mVirtualDisplayCreated = false; 425 private OverlayDisplayDevicesSession mOverlayDisplayDeviceSession; 426 setDensityDpi(int densityDpi)427 VirtualDisplaySession setDensityDpi(int densityDpi) { 428 mDensityDpi = densityDpi; 429 return this; 430 } 431 setLaunchInSplitScreen(boolean launchInSplitScreen)432 VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) { 433 mLaunchInSplitScreen = launchInSplitScreen; 434 return this; 435 } 436 setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)437 VirtualDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) { 438 mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard; 439 return this; 440 } 441 setPublicDisplay(boolean publicDisplay)442 VirtualDisplaySession setPublicDisplay(boolean publicDisplay) { 443 mPublicDisplay = publicDisplay; 444 return this; 445 } 446 setResizeDisplay(boolean resizeDisplay)447 VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) { 448 mResizeDisplay = resizeDisplay; 449 return this; 450 } 451 setShowSystemDecorations(boolean showSystemDecorations)452 VirtualDisplaySession setShowSystemDecorations(boolean showSystemDecorations) { 453 mShowSystemDecorations = showSystemDecorations; 454 return this; 455 } 456 setOwnContentOnly(boolean ownContentOnly)457 VirtualDisplaySession setOwnContentOnly(boolean ownContentOnly) { 458 mOwnContentOnly = ownContentOnly; 459 return this; 460 } 461 462 /** 463 * Sets the policy for how the display should show the ime. 464 * 465 * Set to one of: 466 * <ul> 467 * <li>{@link WindowManager#DISPLAY_IME_POLICY_LOCAL} 468 * <li>{@link WindowManager#DISPLAY_IME_POLICY_FALLBACK_DISPLAY} 469 * <li>{@link WindowManager#DISPLAY_IME_POLICY_HIDE} 470 * </ul> 471 */ setDisplayImePolicy(int displayImePolicy)472 VirtualDisplaySession setDisplayImePolicy(int displayImePolicy) { 473 mDisplayImePolicy = displayImePolicy; 474 return this; 475 } 476 setPresentationDisplay(boolean presentationDisplay)477 VirtualDisplaySession setPresentationDisplay(boolean presentationDisplay) { 478 mPresentationDisplay = presentationDisplay; 479 return this; 480 } 481 482 // TODO(b/154565343) move simulate display out of VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay)483 public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) { 484 mSimulateDisplay = simulateDisplay; 485 return this; 486 } 487 setSimulationDisplaySize(int width, int height)488 VirtualDisplaySession setSimulationDisplaySize(int width, int height) { 489 mSimulationDisplaySize = new Size(width, height); 490 return this; 491 } 492 493 @Nullable createDisplay(boolean mustBeCreated)494 public DisplayContent createDisplay(boolean mustBeCreated) { 495 mMustBeCreated = mustBeCreated; 496 final DisplayContent display = createDisplays(1).stream().findFirst().orElse(null); 497 if (mustBeCreated && display == null) { 498 throw new IllegalStateException("No display is created"); 499 } 500 return display; 501 } 502 503 @NonNull createDisplay()504 public DisplayContent createDisplay() { 505 return Objects.requireNonNull(createDisplay(true /* mustBeCreated */)); 506 } 507 508 @NonNull createDisplays(int count)509 List<DisplayContent> createDisplays(int count) { 510 if (mSimulateDisplay) { 511 return simulateDisplay(); 512 } else { 513 return createVirtualDisplays(count); 514 } 515 } 516 resizeDisplay()517 void resizeDisplay() { 518 if (mSimulateDisplay) { 519 throw new IllegalStateException( 520 "Please use ReportedDisplayMetrics#setDisplayMetrics to resize" 521 + " simulate display"); 522 } 523 executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) 524 + " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY); 525 } 526 527 @Override close()528 public void close() { 529 if (mOverlayDisplayDeviceSession != null) { 530 mOverlayDisplayDeviceSession.close(); 531 } 532 if (mVirtualDisplayCreated) { 533 destroyVirtualDisplays(); 534 mVirtualDisplayCreated = false; 535 } 536 } 537 538 /** 539 * Simulate new display. 540 * <pre> 541 * <code>mDensityDpi</code> provide custom density for the display. 542 * </pre> 543 * @return {@link DisplayContent} of newly created display. 544 */ simulateDisplay()545 private List<DisplayContent> simulateDisplay() { 546 mOverlayDisplayDeviceSession = new OverlayDisplayDevicesSession(mContext); 547 mOverlayDisplayDeviceSession.createDisplay( 548 mSimulationDisplaySize, 549 mDensityDpi, 550 mOwnContentOnly, 551 mShowSystemDecorations); 552 mOverlayDisplayDeviceSession.configureDisplays(mDisplayImePolicy /* imePolicy */); 553 return mOverlayDisplayDeviceSession.getCreatedDisplays(); 554 } 555 556 /** 557 * Create new virtual display. 558 * <pre> 559 * <code>mDensityDpi</code> provide custom density for the display. 560 * <code>mLaunchInSplitScreen</code> start 561 * {@link android.server.wm.app.VirtualDisplayActivity} to side from 562 * {@link android.server.wm.app.LaunchingActivity} on primary display. 563 * <code>mCanShowWithInsecureKeyguard</code> allow showing content when device is 564 * showing an insecure keyguard. 565 * <code>mMustBeCreated</code> should assert if the display was or wasn't created. 566 * <code>mPublicDisplay</code> make display public. 567 * <code>mResizeDisplay</code> should resize display when surface size changes. 568 * <code>LaunchActivity</code> should launch test activity immediately after display 569 * creation. 570 * </pre> 571 * @param displayCount number of displays to be created. 572 * @return A list of {@link DisplayContent} that represent newly created displays. 573 * @throws Exception 574 */ createVirtualDisplays(int displayCount)575 private List<DisplayContent> createVirtualDisplays(int displayCount) { 576 // Start an activity that is able to create virtual displays. 577 if (mLaunchInSplitScreen) { 578 getLaunchActivityBuilder() 579 .setToSide(true) 580 .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY) 581 .execute(); 582 final int secondaryTaskId = 583 mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY).mTaskId; 584 mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId); 585 } else { 586 launchActivity(VIRTUAL_DISPLAY_ACTIVITY); 587 } 588 mWmState.computeState( 589 new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY)); 590 mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); 591 mWmState.assertFocusedActivity("Focus must be on virtual display host activity", 592 VIRTUAL_DISPLAY_ACTIVITY); 593 final List<DisplayContent> originalDS = getDisplaysStates(); 594 595 // Create virtual display with custom density dpi. 596 final StringBuilder createVirtualDisplayCommand = new StringBuilder( 597 getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)) 598 .append(" -f 0x20000000") 599 .append(" --es " + KEY_COMMAND + " " + COMMAND_CREATE_DISPLAY); 600 if (mDensityDpi != INVALID_DENSITY_DPI) { 601 createVirtualDisplayCommand 602 .append(" --ei " + KEY_DENSITY_DPI + " ") 603 .append(mDensityDpi); 604 } 605 createVirtualDisplayCommand.append(" --ei " + KEY_COUNT + " ").append(displayCount) 606 .append(" --ez " + KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD + " ") 607 .append(mCanShowWithInsecureKeyguard) 608 .append(" --ez " + KEY_PUBLIC_DISPLAY + " ").append(mPublicDisplay) 609 .append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay) 610 .append(" --ez " + KEY_SHOW_SYSTEM_DECORATIONS + " ") 611 .append(mShowSystemDecorations) 612 .append(" --ez " + KEY_PRESENTATION_DISPLAY + " ").append(mPresentationDisplay); 613 executeShellCommand(createVirtualDisplayCommand.toString()); 614 mVirtualDisplayCreated = true; 615 616 return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS); 617 } 618 619 /** 620 * Destroy existing virtual display. 621 */ destroyVirtualDisplays()622 void destroyVirtualDisplays() { 623 final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) 624 + " -f 0x20000000" 625 + " --es " + KEY_COMMAND + " " + COMMAND_DESTROY_DISPLAY; 626 executeShellCommand(destroyVirtualDisplayCommand); 627 waitForDisplayGone( 628 d -> d.getName() != null && d.getName().contains(VIRTUAL_DISPLAY_PREFIX)); 629 } 630 } 631 632 // TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated. 633 protected class VirtualDisplayLauncher extends VirtualDisplaySession { 634 private final ActivitySessionClient mActivitySessionClient = createActivitySessionClient(); 635 launchActivityOnDisplay(ComponentName activityName, DisplayContent display)636 ActivitySession launchActivityOnDisplay(ComponentName activityName, 637 DisplayContent display) { 638 return launchActivityOnDisplay(activityName, display, null /* extrasConsumer */, 639 true /* withShellPermission */, true /* waitForLaunch */); 640 } 641 launchActivityOnDisplay(ComponentName activityName, DisplayContent display, Consumer<Bundle> extrasConsumer, boolean withShellPermission, boolean waitForLaunch)642 ActivitySession launchActivityOnDisplay(ComponentName activityName, 643 DisplayContent display, Consumer<Bundle> extrasConsumer, 644 boolean withShellPermission, boolean waitForLaunch) { 645 return launchActivity(builder -> builder 646 // VirtualDisplayActivity is in different package. If the display is not public, 647 // it requires shell permission to launch activity ({@see com.android.server.wm. 648 // ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay}). 649 .setWithShellPermission(withShellPermission) 650 .setWaitForLaunched(waitForLaunch) 651 .setIntentExtra(extrasConsumer) 652 .setTargetActivity(activityName) 653 .setDisplayId(display.mId)); 654 } 655 launchActivity(Consumer<LaunchActivityBuilder> setupBuilder)656 ActivitySession launchActivity(Consumer<LaunchActivityBuilder> setupBuilder) { 657 final LaunchActivityBuilder builder = getLaunchActivityBuilder() 658 .setUseInstrumentation(); 659 setupBuilder.accept(builder); 660 return mActivitySessionClient.startActivity(builder); 661 } 662 663 @Override close()664 public void close() { 665 super.close(); 666 mActivitySessionClient.close(); 667 } 668 } 669 670 /** Helper class to save, set, and restore overlay_display_devices preference. */ 671 private class OverlayDisplayDevicesSession extends SettingsSession<String> { 672 /** See display_manager_overlay_display_name. */ 673 private static final String OVERLAY_DISPLAY_NAME_PREFIX = "Overlay #"; 674 675 /** See {@link OverlayDisplayAdapter#OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY}. */ 676 private static final String OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY = ",own_content_only"; 677 678 /** 679 * See {@link OverlayDisplayAdapter#OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. 680 */ 681 private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 682 ",should_show_system_decorations"; 683 684 /** The displays which are created by this session. */ 685 private final List<DisplayContent> mDisplays = new ArrayList<>(); 686 /** The configured displays that need to be restored when this session is closed. */ 687 private final List<OverlayDisplayState> mDisplayStates = new ArrayList<>(); 688 private final WindowManager mWm; 689 OverlayDisplayDevicesSession(Context context)690 OverlayDisplayDevicesSession(Context context) { 691 super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES), 692 Settings.Global::getString, 693 Settings.Global::putString); 694 // Remove existing overlay display to avoid display count problem. 695 removeExisting(); 696 mWm = context.getSystemService(WindowManager.class); 697 } 698 getCreatedDisplays()699 List<DisplayContent> getCreatedDisplays() { 700 return new ArrayList<>(mDisplays); 701 } 702 703 @Override set(String value)704 public void set(String value) { 705 final List<DisplayContent> originalDisplays = getDisplaysStates(); 706 super.set(value); 707 final int newDisplayCount = 1 + (int) value.chars().filter(ch -> ch == ';').count(); 708 mDisplays.addAll(assertAndGetNewDisplays(newDisplayCount, originalDisplays)); 709 } 710 711 /** Creates overlay display with custom density dpi, specified size, and test flags. */ createDisplay(Size displaySize, int densityDpi, boolean ownContentOnly, boolean shouldShowSystemDecorations)712 void createDisplay(Size displaySize, int densityDpi, boolean ownContentOnly, 713 boolean shouldShowSystemDecorations) { 714 String displaySettingsEntry = displaySize + "/" + densityDpi; 715 if (ownContentOnly) { 716 displaySettingsEntry += OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY; 717 } 718 if (shouldShowSystemDecorations) { 719 displaySettingsEntry += OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; 720 } 721 set(displaySettingsEntry); 722 } 723 configureDisplays(int imePolicy)724 void configureDisplays(int imePolicy) { 725 SystemUtil.runWithShellPermissionIdentity(() -> { 726 for (DisplayContent display : mDisplays) { 727 final int oldImePolicy = mWm.getDisplayImePolicy(display.mId); 728 mDisplayStates.add(new OverlayDisplayState(display.mId, oldImePolicy)); 729 if (imePolicy != oldImePolicy) { 730 mWm.setDisplayImePolicy(display.mId, imePolicy); 731 waitForOrFail("display config show-IME to be set", 732 () -> (mWm.getDisplayImePolicy(display.mId) == imePolicy)); 733 } 734 } 735 }); 736 } 737 restoreDisplayStates()738 private void restoreDisplayStates() { 739 mDisplayStates.forEach(state -> SystemUtil.runWithShellPermissionIdentity(() -> { 740 mWm.setDisplayImePolicy(state.mId, state.mImePolicy); 741 742 // Only need to wait the last flag to be set. 743 waitForOrFail("display config show-IME to be restored", 744 () -> (mWm.getDisplayImePolicy(state.mId) == state.mImePolicy)); 745 })); 746 } 747 748 @Override close()749 public void close() { 750 // Need to restore display state before display is destroyed. 751 restoreDisplayStates(); 752 super.close(); 753 // Waiting for restoring to the state before this session was created. 754 waitForDisplayGone(display -> mDisplays.stream() 755 .anyMatch(createdDisplay -> createdDisplay.mId == display.mId)); 756 } 757 removeExisting()758 private void removeExisting() { 759 if (!mHasInitialValue || mInitialValue == null) { 760 // No existing overlay displays. 761 return; 762 } 763 delete(mUri); 764 // Make sure all overlay displays are completely removed. 765 waitForDisplayGone( 766 display -> display.getName().startsWith(OVERLAY_DISPLAY_NAME_PREFIX)); 767 } 768 769 private class OverlayDisplayState { 770 int mId; 771 int mImePolicy; 772 OverlayDisplayState(int displayId, int imePolicy)773 OverlayDisplayState(int displayId, int imePolicy) { 774 mId = displayId; 775 mImePolicy = imePolicy; 776 } 777 } 778 } 779 780 /** Wait for provided number of displays and report their configurations. */ getDisplayStateAfterChange(int expectedDisplayCount)781 List<DisplayContent> getDisplayStateAfterChange(int expectedDisplayCount) { 782 return Condition.waitForResult("the correct number of displays=" + expectedDisplayCount, 783 condition -> condition 784 .setReturnLastResult(true) 785 .setResultSupplier(this::getDisplaysStates) 786 .setResultValidator( 787 displays -> areDisplaysValid(displays, expectedDisplayCount))); 788 } 789 areDisplaysValid(List<DisplayContent> displays, int expectedDisplayCount)790 private boolean areDisplaysValid(List<DisplayContent> displays, int expectedDisplayCount) { 791 if (displays.size() != expectedDisplayCount) { 792 return false; 793 } 794 for (DisplayContent display : displays) { 795 if (display.mOverrideConfiguration.densityDpi == 0) { 796 return false; 797 } 798 } 799 return true; 800 } 801 802 /** 803 * Wait for desired number of displays to be created and get their properties. 804 * 805 * @param newDisplayCount expected display count, -1 if display should not be created. 806 * @param originalDisplays display states before creation of new display(s). 807 * @return list of new displays, empty list if no new display is created. 808 */ assertAndGetNewDisplays(int newDisplayCount, List<DisplayContent> originalDisplays)809 private List<DisplayContent> assertAndGetNewDisplays(int newDisplayCount, 810 List<DisplayContent> originalDisplays) { 811 final int originalDisplayCount = originalDisplays.size(); 812 813 // Wait for the display(s) to be created and get configurations. 814 final List<DisplayContent> ds = getDisplayStateAfterChange( 815 originalDisplayCount + newDisplayCount); 816 if (newDisplayCount != -1) { 817 assertEquals("New virtual display(s) must be created", 818 originalDisplayCount + newDisplayCount, ds.size()); 819 } else { 820 assertEquals("New virtual display must not be created", 821 originalDisplayCount, ds.size()); 822 return Collections.emptyList(); 823 } 824 825 // Find the newly added display(s). 826 final List<DisplayContent> newDisplays = findNewDisplayStates(originalDisplays, ds); 827 assertThat("New virtual display must be created", newDisplays, hasSize(newDisplayCount)); 828 829 return newDisplays; 830 } 831 832 /** A clearer alias of {@link Pair#create(Object, Object)}. */ pair(K k, V v)833 protected <K, V> Pair<K, V> pair(K k, V v) { 834 return new Pair<>(k, v); 835 } 836 assertBothDisplaysHaveResumedActivities( Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair)837 protected void assertBothDisplaysHaveResumedActivities( 838 Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair) { 839 assertNotEquals("Displays must be different. First display id: " 840 + firstPair.first, firstPair.first, secondPair.first); 841 mWmState.assertResumedActivities("Both displays must have resumed activities", 842 mapping -> { 843 mapping.put(firstPair.first, firstPair.second); 844 mapping.put(secondPair.first, secondPair.second); 845 }); 846 } 847 848 /** Checks if the device supports multi-display. */ supportsMultiDisplay()849 protected boolean supportsMultiDisplay() { 850 return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS); 851 } 852 853 /** Checks if the device supports wallpaper for multi-display. */ supportsLiveWallpaper()854 protected boolean supportsLiveWallpaper() { 855 return hasDeviceFeature(PackageManager.FEATURE_LIVE_WALLPAPER); 856 } 857 858 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedExternalDisplaySession()859 protected ExternalDisplaySession createManagedExternalDisplaySession() { 860 return mObjectTracker.manage(new ExternalDisplaySession()); 861 } 862 863 @SafeVarargs waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId, Predicate<ImeEvent>... conditions)864 final void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId, 865 Predicate<ImeEvent>... conditions) throws Exception { 866 for (Predicate<ImeEvent> condition : conditions) { 867 expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */); 868 } 869 // Assert the IME is shown on the expected display. 870 mWmState.waitAndAssertImeWindowShownOnDisplay(displayId); 871 } 872 873 /** 874 * This class is used when you need to test virtual display created by a privileged app. 875 * 876 * If you need to test virtual display created by a non-privileged app or when you need to test 877 * on simulated display, please use {@link VirtualDisplaySession} instead. 878 */ 879 public class ExternalDisplaySession implements AutoCloseable { 880 881 private boolean mCanShowWithInsecureKeyguard = false; 882 private boolean mPublicDisplay = false; 883 private boolean mShowSystemDecorations = false; 884 885 private int mDisplayId = INVALID_DISPLAY; 886 887 @Nullable 888 private VirtualDisplayHelper mExternalDisplayHelper; 889 setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)890 ExternalDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) { 891 mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard; 892 return this; 893 } 894 setPublicDisplay(boolean publicDisplay)895 ExternalDisplaySession setPublicDisplay(boolean publicDisplay) { 896 mPublicDisplay = publicDisplay; 897 return this; 898 } 899 900 /** 901 * @deprecated untrusted virtual display won't have system decorations even it has the flag. 902 * Only use this method to verify that. To test secondary display with system decorations, 903 * please use simulated display. 904 */ 905 @Deprecated setShowSystemDecorations(boolean showSystemDecorations)906 ExternalDisplaySession setShowSystemDecorations(boolean showSystemDecorations) { 907 mShowSystemDecorations = showSystemDecorations; 908 return this; 909 } 910 911 /** 912 * Creates a private virtual display with insecure keyguard flags set. 913 */ createVirtualDisplay()914 DisplayContent createVirtualDisplay() { 915 final List<DisplayContent> originalDS = getDisplaysStates(); 916 final int originalDisplayCount = originalDS.size(); 917 918 mExternalDisplayHelper = new VirtualDisplayHelper(); 919 mExternalDisplayHelper 920 .setPublicDisplay(mPublicDisplay) 921 .setCanShowWithInsecureKeyguard(mCanShowWithInsecureKeyguard) 922 .setShowSystemDecorations(mShowSystemDecorations) 923 .createAndWaitForDisplay(); 924 925 // Wait for the virtual display to be created and get configurations. 926 final List<DisplayContent> ds = getDisplayStateAfterChange(originalDisplayCount + 1); 927 assertEquals("New virtual display must be created", originalDisplayCount + 1, 928 ds.size()); 929 930 // Find the newly added display. 931 final DisplayContent newDisplay = findNewDisplayStates(originalDS, ds).get(0); 932 mDisplayId = newDisplay.mId; 933 return newDisplay; 934 } 935 turnDisplayOff()936 void turnDisplayOff() { 937 if (mExternalDisplayHelper == null) { 938 throw new RuntimeException("No external display created"); 939 } 940 mExternalDisplayHelper.turnDisplayOff(); 941 } 942 turnDisplayOn()943 void turnDisplayOn() { 944 if (mExternalDisplayHelper == null) { 945 throw new RuntimeException("No external display created"); 946 } 947 mExternalDisplayHelper.turnDisplayOn(); 948 } 949 950 @Override close()951 public void close() { 952 if (mExternalDisplayHelper != null) { 953 mExternalDisplayHelper.releaseDisplay(); 954 mExternalDisplayHelper = null; 955 956 waitForDisplayGone(d -> d.mId == mDisplayId); 957 mDisplayId = INVALID_DISPLAY; 958 } 959 } 960 } 961 962 public static class PrimaryDisplayStateSession implements AutoCloseable { 963 turnScreenOff()964 void turnScreenOff() { 965 setPrimaryDisplayState(false); 966 } 967 968 @Override close()969 public void close() { 970 setPrimaryDisplayState(true); 971 } 972 973 /** Turns the primary display on/off by pressing the power key */ setPrimaryDisplayState(boolean wantOn)974 private void setPrimaryDisplayState(boolean wantOn) { 975 if (wantOn) { 976 pressWakeupButton(); 977 } else { 978 pressSleepButton(); 979 } 980 VirtualDisplayHelper.waitForDefaultDisplayState(wantOn); 981 } 982 } 983 } 984