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