1 /* 2 * Copyright (C) 2013 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.display.cts; 18 19 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; 20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; 21 22 import static androidx.test.core.app.ApplicationProvider.getApplicationContext; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertNull; 28 import static org.junit.Assert.assertThrows; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assume.assumeFalse; 31 import static org.junit.Assume.assumeTrue; 32 33 import android.Manifest; 34 import android.app.ActivityOptions; 35 import android.app.Presentation; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.pm.ActivityInfo; 39 import android.content.pm.PackageManager; 40 import android.content.res.Resources; 41 import android.graphics.Color; 42 import android.graphics.Insets; 43 import android.graphics.PixelFormat; 44 import android.graphics.Point; 45 import android.graphics.Rect; 46 import android.graphics.drawable.ColorDrawable; 47 import android.hardware.display.DisplayManager; 48 import android.hardware.display.VirtualDisplay; 49 import android.hardware.display.VirtualDisplayConfig; 50 import android.media.Image; 51 import android.media.ImageReader; 52 import android.os.Bundle; 53 import android.os.Handler; 54 import android.os.HandlerThread; 55 import android.os.Looper; 56 import android.os.SystemClock; 57 import android.platform.test.annotations.AppModeSdkSandbox; 58 import android.platform.test.annotations.AsbSecurityTest; 59 import android.platform.test.annotations.RequiresFlagsEnabled; 60 import android.platform.test.flag.junit.CheckFlagsRule; 61 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 62 import android.provider.Settings; 63 import android.server.wm.IgnoreOrientationRequestSession; 64 import android.server.wm.UiDeviceUtils; 65 import android.server.wm.WindowManagerStateHelper; 66 import android.util.DisplayMetrics; 67 import android.util.Log; 68 import android.view.Display; 69 import android.view.DisplayCutout; 70 import android.view.Surface; 71 import android.view.ViewGroup.LayoutParams; 72 import android.widget.ImageView; 73 74 import androidx.test.ext.junit.runners.AndroidJUnit4; 75 import androidx.test.platform.app.InstrumentationRegistry; 76 77 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 78 import com.android.compatibility.common.util.DisplayStateManager; 79 import com.android.compatibility.common.util.FeatureUtil; 80 import com.android.compatibility.common.util.SettingsStateKeeperRule; 81 import com.android.compatibility.common.util.StateKeeperRule; 82 import com.android.compatibility.common.util.SystemUtil; 83 import com.android.compatibility.common.util.UserSettings.Namespace; 84 85 import org.junit.After; 86 import org.junit.Before; 87 import org.junit.ClassRule; 88 import org.junit.Rule; 89 import org.junit.Test; 90 import org.junit.runner.RunWith; 91 92 import java.nio.ByteBuffer; 93 import java.util.concurrent.CountDownLatch; 94 import java.util.concurrent.TimeUnit; 95 import java.util.concurrent.locks.Lock; 96 import java.util.concurrent.locks.ReentrantLock; 97 98 /** 99 * Tests that applications can create virtual displays and present content on them. 100 * 101 * This CTS test is unable to test public virtual displays since special permissions 102 * are required. See also framework VirtualDisplayTest unit tests. 103 */ 104 @RunWith(AndroidJUnit4.class) 105 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") 106 public class VirtualDisplayTest { 107 private static final String TAG = "VirtualDisplayTest"; 108 109 private static final String NAME = TAG; 110 private static final int WIDTH = 720; 111 private static final int HEIGHT = 480; 112 private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM; 113 private static final float REQUESTED_REFRESH_RATE = 30.0f; 114 private static final int TIMEOUT = 40000; 115 116 // Colors that we use as a signal to determine whether some desired content was 117 // drawn. The colors themselves doesn't matter but we choose them to have with distinct 118 // values for each color channel so as to detect possible RGBA vs. BGRA buffer format issues. 119 // We should only observe RGBA buffers but some graphics drivers might incorrectly 120 // deliver BGRA buffers to virtual displays instead. 121 private static final int BLUEISH = 0xff1122ee; 122 private static final int GREENISH = 0xff33dd44; 123 124 private Context mContext; 125 private DisplayManager mDisplayManager; 126 private WindowManagerStateHelper mWindowManagerStateHelper; 127 private Handler mHandler; 128 private final Lock mImageReaderLock = new ReentrantLock(true /*fair*/); 129 private ImageReader mImageReader; 130 private Surface mSurface; 131 private ImageListener mImageListener; 132 private HandlerThread mCheckThread; 133 private Handler mCheckHandler; 134 135 @Rule(order = 0) 136 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = 137 new AdoptShellPermissionsRule( 138 InstrumentationRegistry.getInstrumentation().getUiAutomation(), 139 Manifest.permission.ADD_TRUSTED_DISPLAY, 140 Manifest.permission.WRITE_SECURE_SETTINGS); 141 142 @ClassRule 143 public static final SettingsStateKeeperRule mAreUserDisabledHdrFormatsAllowedSettingsKeeper = 144 new SettingsStateKeeperRule( 145 InstrumentationRegistry.getInstrumentation().getTargetContext(), 146 Namespace.GLOBAL, Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED); 147 148 @ClassRule 149 public static final SettingsStateKeeperRule mUserDisabledHdrFormatsSettingsKeeper = 150 new SettingsStateKeeperRule( 151 InstrumentationRegistry.getInstrumentation().getTargetContext(), 152 Namespace.GLOBAL, Settings.Global.USER_DISABLED_HDR_FORMATS); 153 154 @Rule(order = 1) 155 public StateKeeperRule<DisplayStateManager.DisplayState> mDisplayManagerStateKeeper = 156 new StateKeeperRule<>(new DisplayStateManager( 157 InstrumentationRegistry.getInstrumentation().getTargetContext())); 158 159 @Rule 160 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 161 162 @Before setUp()163 public void setUp() throws Exception { 164 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 165 mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE); 166 mWindowManagerStateHelper = new WindowManagerStateHelper(); 167 mHandler = new Handler(Looper.getMainLooper()); 168 mImageListener = new ImageListener(); 169 // thread for image checking 170 mCheckThread = new HandlerThread("TestHandler"); 171 mCheckThread.start(); 172 mCheckHandler = new Handler(mCheckThread.getLooper()); 173 174 mImageReaderLock.lock(); 175 try { 176 mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2); 177 mImageReader.setOnImageAvailableListener(mImageListener, mCheckHandler); 178 mSurface = mImageReader.getSurface(); 179 } finally { 180 mImageReaderLock.unlock(); 181 } 182 } 183 184 @After tearDown()185 public void tearDown() throws Exception { 186 mImageReaderLock.lock(); 187 try { 188 mImageReader.close(); 189 mImageReader = null; 190 mSurface = null; 191 } finally { 192 mImageReaderLock.unlock(); 193 } 194 mCheckThread.quit(); 195 } 196 197 /** 198 * Ensures that an application can create a private virtual display and show 199 * its own windows on it. 200 */ 201 @Test 202 @AsbSecurityTest(cveBugId = 141745510) testPrivateVirtualDisplay()203 public void testPrivateVirtualDisplay() throws Exception { 204 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 205 WIDTH, HEIGHT, DENSITY, mSurface, 0); 206 assertNotNull("virtual display must not be null", virtualDisplay); 207 208 Display display = virtualDisplay.getDisplay(); 209 try { 210 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 211 assertEquals(mSurface, virtualDisplay.getSurface()); 212 213 // Show a private presentation on the display. 214 assertDisplayCanShowPresentation("private presentation window", 215 display, BLUEISH, 0); 216 } finally { 217 virtualDisplay.release(); 218 } 219 assertDisplayUnregistered(display); 220 } 221 222 /** 223 * Ensures that an application can create a private presentation virtual display and show 224 * its own windows on it. 225 */ 226 @Test 227 @AsbSecurityTest(cveBugId = 141745510) testPrivatePresentationVirtualDisplay()228 public void testPrivatePresentationVirtualDisplay() throws Exception { 229 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 230 WIDTH, HEIGHT, DENSITY, mSurface, 231 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); 232 assertNotNull("virtual display must not be null", virtualDisplay); 233 234 Display display = virtualDisplay.getDisplay(); 235 try { 236 assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION); 237 assertEquals(mSurface, virtualDisplay.getSurface()); 238 239 // Show a private presentation on the display. 240 assertDisplayCanShowPresentation("private presentation window", 241 display, BLUEISH, 0); 242 } finally { 243 virtualDisplay.release(); 244 } 245 assertDisplayUnregistered(display); 246 } 247 248 /** 249 * Ensures that an application can create a private virtual display and show 250 * its own windows on it where the surface is attached or detached dynamically. 251 */ 252 @Test 253 @AsbSecurityTest(cveBugId = 141745510) testPrivateVirtualDisplayWithDynamicSurface()254 public void testPrivateVirtualDisplayWithDynamicSurface() throws Exception { 255 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 256 WIDTH, HEIGHT, DENSITY, null, 0); 257 assertNotNull("virtual display must not be null", virtualDisplay); 258 259 Display display = virtualDisplay.getDisplay(); 260 try { 261 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 262 assertNull(virtualDisplay.getSurface()); 263 264 // Attach the surface. 265 virtualDisplay.setSurface(mSurface); 266 assertEquals(mSurface, virtualDisplay.getSurface()); 267 268 // Show a private presentation on the display. 269 assertDisplayCanShowPresentation("private presentation window", 270 display, BLUEISH, 0); 271 272 // Detach the surface. 273 virtualDisplay.setSurface(null); 274 assertNull(virtualDisplay.getSurface()); 275 } finally { 276 virtualDisplay.release(); 277 } 278 assertDisplayUnregistered(display); 279 } 280 281 /** 282 * Ensures that {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} will 283 * be clear if an application creates an virtual display without the 284 * flag {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED}. 285 */ 286 @Test testUntrustedSysDecorVirtualDisplay()287 public void testUntrustedSysDecorVirtualDisplay() throws Exception { 288 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 289 WIDTH, HEIGHT, DENSITY, mSurface, 290 VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS); 291 assertNotNull("virtual display must not be null", virtualDisplay); 292 293 Display display = virtualDisplay.getDisplay(); 294 try { 295 // Verify that the created virtual display doesn't have flags 296 // FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS. 297 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 298 assertEquals(mSurface, virtualDisplay.getSurface()); 299 300 // Show a private presentation on the display. 301 assertDisplayCanShowPresentation("private presentation window", 302 display, BLUEISH, 0); 303 } finally { 304 virtualDisplay.release(); 305 } 306 assertDisplayUnregistered(display); 307 } 308 309 /** 310 * Ensures that throws {@link SecurityException} when an application creates a trusted virtual 311 * display without holding the permission {@code ADD_TRUSTED_DISPLAY}. 312 */ 313 @Test testTrustedVirtualDisplay()314 public void testTrustedVirtualDisplay() { 315 InstrumentationRegistry.getInstrumentation() 316 .getUiAutomation() 317 .dropShellPermissionIdentity(); 318 assertThrows( 319 "SecurityException must be thrown if a trusted virtual display is created without" 320 + "holding the permission ADD_TRUSTED_DISPLAY.", 321 SecurityException.class, 322 () -> mDisplayManager.createVirtualDisplay( 323 NAME, WIDTH, HEIGHT, DENSITY, mSurface, VIRTUAL_DISPLAY_FLAG_TRUSTED)); 324 } 325 326 /** 327 * Ensures that detaching the display surface turns the display off and attaching a surface will 328 * turn it on only if the power group is already on. 329 */ 330 @Test testSetSurface_togglesDisplayState()331 public void testSetSurface_togglesDisplayState() throws Exception { 332 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 333 WIDTH, HEIGHT, DENSITY, mSurface, 334 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 335 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 336 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 337 Display display = virtualDisplay.getDisplay(); 338 launchTestActivityOnDisplay(display.getDisplayId()); 339 340 try { 341 assertEquals(display.getState(), Display.STATE_ON); 342 { 343 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 344 virtualDisplay.setSurface(null); 345 assertTrue(waiter.stateChanged()); 346 assertEquals(display.getState(), Display.STATE_OFF); 347 } 348 { 349 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 350 virtualDisplay.setSurface(mSurface); 351 assertTrue(waiter.stateChanged()); 352 assertEquals(display.getState(), Display.STATE_ON); 353 } 354 } finally { 355 virtualDisplay.release(); 356 } 357 assertDisplayUnregistered(display); 358 } 359 360 /** 361 * Ensures that the power group state is propagated to the display state and that attaching the 362 * display surface does not turn on the display if the power group is off. 363 */ 364 @Test testSetSurface_powerGroupOff_doesNotTurnOnDisplay()365 public void testSetSurface_powerGroupOff_doesNotTurnOnDisplay() throws Exception { 366 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 367 WIDTH, HEIGHT, DENSITY, mSurface, 368 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 369 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 370 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 371 Display display = virtualDisplay.getDisplay(); 372 launchTestActivityOnDisplay(display.getDisplayId()); 373 374 try { 375 assertEquals(display.getState(), Display.STATE_ON); 376 { 377 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 378 UiDeviceUtils.pressSleepButton(); 379 assertTrue(waiter.stateChanged()); 380 assertEquals(display.getState(), Display.STATE_OFF); 381 } 382 { 383 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 384 virtualDisplay.setSurface(null); 385 assertFalse(waiter.stateChanged()); 386 assertEquals(display.getState(), Display.STATE_OFF); 387 } 388 { 389 // Attaching a surface does not turn on the display because the power group is off. 390 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 391 virtualDisplay.setSurface(mSurface); 392 assertFalse(waiter.stateChanged()); 393 assertEquals(display.getState(), Display.STATE_OFF); 394 } 395 { 396 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 397 UiDeviceUtils.pressWakeupButton(); 398 assertTrue(waiter.stateChanged()); 399 assertEquals(display.getState(), Display.STATE_ON); 400 } 401 } finally { 402 UiDeviceUtils.pressWakeupButton(); 403 virtualDisplay.release(); 404 } 405 assertDisplayUnregistered(display); 406 } 407 408 /** 409 * Ensures that the power group state is reflected in the display state upon its creation. 410 */ 411 @Test testCreateDisplay_nonNullSurface_powerGroupOff_displayStateIsOff()412 public void testCreateDisplay_nonNullSurface_powerGroupOff_displayStateIsOff() { 413 assumeScreenOffSupported(); 414 415 VirtualDisplay virtualDisplay = null; 416 Display display = null; 417 try { 418 UiDeviceUtils.pressSleepButton(); 419 virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 420 WIDTH, HEIGHT, DENSITY, mSurface, 421 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 422 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 423 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 424 display = virtualDisplay.getDisplay(); 425 launchTestActivityOnDisplay(display.getDisplayId()); 426 assertEquals(display.getState(), Display.STATE_OFF); 427 } finally { 428 UiDeviceUtils.pressWakeupButton(); 429 if (virtualDisplay != null) { 430 virtualDisplay.release(); 431 } 432 } 433 if (display != null) { 434 assertDisplayUnregistered(display); 435 } 436 } 437 438 /** 439 * Ensures that an application can create a private virtual display with a requested 440 * refresh rate and show its own windows on it. 441 */ 442 @Test testVirtualDisplayWithRequestedRefreshRate()443 public void testVirtualDisplayWithRequestedRefreshRate() throws Exception { 444 VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY) 445 .setSurface(mSurface) 446 .setRequestedRefreshRate(REQUESTED_REFRESH_RATE) 447 .build(); 448 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(config); 449 assertNotNull("virtual display must not be null", virtualDisplay); 450 Display display = virtualDisplay.getDisplay(); 451 try { 452 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 453 assertEquals(mSurface, virtualDisplay.getSurface()); 454 455 assertEquals(display.getRefreshRate(), REQUESTED_REFRESH_RATE, 0.1f); 456 } finally { 457 virtualDisplay.release(); 458 } 459 assertDisplayUnregistered(display); 460 } 461 462 /** 463 * Ensures that an application can create a virtual display without a cutout. 464 */ 465 @Test testVirtualDisplayWithoutCutout()466 public void testVirtualDisplayWithoutCutout() { 467 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay( 468 new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY) 469 .setSurface(mSurface) 470 .build()); 471 try { 472 assertNull(virtualDisplay.getDisplay().getCutout()); 473 } finally { 474 virtualDisplay.release(); 475 } 476 assertDisplayUnregistered(virtualDisplay.getDisplay()); 477 } 478 479 /** 480 * Ensures that an application can create a virtual display with a cutout. 481 */ 482 @RequiresFlagsEnabled(android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_INSETS) 483 @Test testVirtualDisplayWithCutout()484 public void testVirtualDisplayWithCutout() { 485 DisplayCutout cutout = new DisplayCutout( 486 /* safeInsets= */ Insets.of(0, 0, 0, 0), 487 /* boundLeft= */ new Rect(5, 6, 7, 8), 488 /* boundTop= */ new Rect(9, 10, 11, 12), 489 /* boundRight= */ new Rect(13, 14, 15, 16), 490 /* boundBottom= */ new Rect(17, 18, 19, 20), 491 /* waterfallInsets= */ Insets.of(21, 22, 23, 24)); 492 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay( 493 new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY) 494 .setSurface(mSurface) 495 .setDisplayCutout(cutout) 496 .build()); 497 try { 498 // The safe insets are always computed by WindowManager based on the waterfall insets 499 // and the bounds. The values passed to the DisplayCutout constructor don't matter as 500 // they will be overridden by WindowManager. So do not validate the safe insets. 501 DisplayCutout actualCutout = virtualDisplay.getDisplay().getCutout(); 502 assertEquals(actualCutout.getBoundingRectLeft(), cutout.getBoundingRectLeft()); 503 assertEquals(actualCutout.getBoundingRectTop(), cutout.getBoundingRectTop()); 504 assertEquals(actualCutout.getBoundingRectRight(), cutout.getBoundingRectRight()); 505 assertEquals(actualCutout.getBoundingRectBottom(), cutout.getBoundingRectBottom()); 506 assertEquals(actualCutout.getWaterfallInsets(), cutout.getWaterfallInsets()); 507 } finally { 508 virtualDisplay.release(); 509 } 510 assertDisplayUnregistered(virtualDisplay.getDisplay()); 511 } 512 513 @Test testVirtualDisplayRotatesWithContent()514 public void testVirtualDisplayRotatesWithContent() throws Exception { 515 assumeTrue(supportsRotation()); 516 517 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 518 WIDTH, HEIGHT, DENSITY, mSurface, 519 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 520 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 521 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY 522 | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT); 523 assertNotNull("virtual display must not be null", virtualDisplay); 524 525 Display display = virtualDisplay.getDisplay(); 526 assertEquals(Surface.ROTATION_0, display.getRotation()); 527 SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId()); 528 try (IgnoreOrientationRequestSession unused = 529 new IgnoreOrientationRequestSession(/* enable= */ false)) { 530 { 531 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 532 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 533 assertTrue(waiter.rotationChanged()); 534 assertEquals(getExpectedPortraitRotation(), display.getRotation()); 535 } 536 { 537 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 538 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 539 assertTrue(waiter.rotationChanged()); 540 assertEquals(Surface.ROTATION_0, display.getRotation()); 541 } 542 } finally { 543 virtualDisplay.release(); 544 } 545 assertDisplayUnregistered(display); 546 } 547 548 @Test testVirtualDisplayDoesNotRotateWithContent()549 public void testVirtualDisplayDoesNotRotateWithContent() throws Exception { 550 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 551 WIDTH, HEIGHT, DENSITY, mSurface, 552 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 553 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 554 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 555 assertNotNull("virtual display must not be null", virtualDisplay); 556 557 Display display = virtualDisplay.getDisplay(); 558 assertEquals(Surface.ROTATION_0, display.getRotation()); 559 SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId()); 560 try { 561 { 562 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 563 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 564 assertFalse(waiter.rotationChanged()); 565 assertEquals(Surface.ROTATION_0, display.getRotation()); 566 } 567 { 568 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 569 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 570 assertFalse(waiter.rotationChanged()); 571 assertEquals(Surface.ROTATION_0, display.getRotation()); 572 } 573 } finally { 574 // Clean up after the test completes. 575 activity.finish(); 576 virtualDisplay.release(); 577 } 578 assertDisplayUnregistered(display); 579 } 580 581 @Test 582 @RequiresFlagsEnabled( 583 android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_ROTATION_API) testRotateVirtualDisplay_invalidRotationValue_throws()584 public void testRotateVirtualDisplay_invalidRotationValue_throws() { 585 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 586 WIDTH, HEIGHT, DENSITY, mSurface, /* flags= */ 0); 587 assertNotNull("virtual display must not be null", virtualDisplay); 588 589 try { 590 assertThrows(IllegalArgumentException.class, () -> virtualDisplay.setRotation(-1)); 591 assertThrows(IllegalArgumentException.class, () -> virtualDisplay.setRotation(4)); 592 } finally { 593 virtualDisplay.release(); 594 } 595 assertDisplayUnregistered(virtualDisplay.getDisplay()); 596 } 597 598 @Test 599 @RequiresFlagsEnabled( 600 android.companion.virtualdevice.flags.Flags.FLAG_VIRTUAL_DISPLAY_ROTATION_API) testRotateVirtualDisplay()601 public void testRotateVirtualDisplay() throws Exception { 602 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 603 WIDTH, HEIGHT, DENSITY, mSurface, 604 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 605 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 606 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 607 assertNotNull("virtual display must not be null", virtualDisplay); 608 609 Display display = virtualDisplay.getDisplay(); 610 assertEquals(Surface.ROTATION_0, display.getRotation()); 611 // Without an activity we're not going to receive display rotation changes 612 launchTestActivityOnDisplay(display.getDisplayId()); 613 try { 614 { 615 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 616 virtualDisplay.setRotation(Surface.ROTATION_270); 617 assertTrue(waiter.rotationChanged()); 618 assertEquals(Surface.ROTATION_270, display.getRotation()); 619 620 } 621 { 622 // Set the current rotation as the new rotation and check that this does NOT 623 // result in a rotation event. 624 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 625 virtualDisplay.setRotation(Surface.ROTATION_270); 626 assertFalse(waiter.rotationChanged()); 627 assertEquals(Surface.ROTATION_270, display.getRotation()); 628 } 629 { 630 DisplayChangeWaiter waiter = new DisplayChangeWaiter(display); 631 virtualDisplay.setRotation(Surface.ROTATION_0); 632 assertTrue(waiter.rotationChanged()); 633 assertEquals(Surface.ROTATION_0, display.getRotation()); 634 } 635 } finally { 636 virtualDisplay.release(); 637 } 638 assertDisplayUnregistered(display); 639 } 640 641 @Test testHdrApiMethods()642 public void testHdrApiMethods() { 643 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 644 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0); 645 try { 646 assertFalse(virtualDisplay.getDisplay().isHdr()); 647 assertNull(virtualDisplay.getDisplay().getHdrCapabilities()); 648 } finally { 649 virtualDisplay.release(); 650 } 651 } 652 653 @Test testGetHdrCapabilitiesWithUserDisabledFormats()654 public void testGetHdrCapabilitiesWithUserDisabledFormats() { 655 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 656 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0); 657 mDisplayManager.setAreUserDisabledHdrTypesAllowed(false); 658 int[] userDisabledHdrTypes = { 659 Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION, 660 Display.HdrCapabilities.HDR_TYPE_HLG}; 661 mDisplayManager.setUserDisabledHdrTypes(userDisabledHdrTypes); 662 663 try { 664 assertFalse(virtualDisplay.getDisplay().isHdr()); 665 assertNull(virtualDisplay.getDisplay().getHdrCapabilities()); 666 } finally { 667 virtualDisplay.release(); 668 } 669 } 670 assertDisplayRegistered(Display display, int flags)671 private void assertDisplayRegistered(Display display, int flags) { 672 assertNotNull("display object must not be null", display); 673 assertTrue("display must be valid", display.isValid()); 674 assertTrue("display id must be unique", 675 display.getDisplayId() != Display.DEFAULT_DISPLAY); 676 assertEquals("display must have correct flags", flags, display.getFlags()); 677 assertEquals("display name must match supplied name", NAME, display.getName()); 678 Point size = new Point(); 679 display.getSize(size); 680 assertEquals("display width must match supplied width", WIDTH, size.x); 681 assertEquals("display height must match supplied height", HEIGHT, size.y); 682 assertEquals("display rotation must be 0", 683 Surface.ROTATION_0, display.getRotation()); 684 assertNotNull("display must be registered", 685 findDisplay(mDisplayManager.getDisplays(), NAME)); 686 687 if ((flags & Display.FLAG_PRESENTATION) != 0) { 688 assertNotNull("display must be registered as a presentation display", 689 findDisplay(mDisplayManager.getDisplays( 690 DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); 691 } else { 692 assertNull("display must not be registered as a presentation display", 693 findDisplay(mDisplayManager.getDisplays( 694 DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); 695 } 696 } 697 assertDisplayUnregistered(Display display)698 private void assertDisplayUnregistered(Display display) { 699 assertNull("display must no longer be registered after being removed", 700 findDisplay(mDisplayManager.getDisplays(), NAME)); 701 assertFalse("display must no longer be valid", display.isValid()); 702 } 703 assertDisplayCanShowPresentation(String message, final Display display, final int color, final int windowFlags)704 private void assertDisplayCanShowPresentation(String message, final Display display, 705 final int color, final int windowFlags) { 706 // At this point, we should not have seen any blue. 707 assertTrue(message + ": display should not show content before window is shown", 708 mImageListener.getColor() != color); 709 710 final TestPresentation[] presentation = new TestPresentation[1]; 711 try { 712 // Show the presentation. 713 runOnUiThread(new Runnable() { 714 @Override 715 public void run() { 716 presentation[0] = new TestPresentation(mContext, display, 717 color, windowFlags); 718 presentation[0].show(); 719 } 720 }); 721 722 // Wait for the blue to be seen. 723 assertTrue(message + ": display should show content after window is shown", 724 mImageListener.waitForColor(color, TIMEOUT)); 725 } finally { 726 if (presentation[0] != null) { 727 runOnUiThread(new Runnable() { 728 @Override 729 public void run() { 730 presentation[0].dismiss(); 731 } 732 }); 733 } 734 } 735 } 736 assumeScreenOffSupported()737 private void assumeScreenOffSupported() { 738 assumeFalse( 739 "Skipping test: Automotive main display is always on", 740 FeatureUtil.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); 741 assumeFalse( 742 "Skipping test: TVs may start screen saver instead of turning screen off", 743 FeatureUtil.hasSystemFeature(PackageManager.FEATURE_LEANBACK)); 744 } 745 launchTestActivityOnDisplay(int displayId)746 private SimpleActivity launchTestActivityOnDisplay(int displayId) { 747 assumeTrue(supportsActivitiesOnSecondaryDisplays()); 748 Intent intent = new Intent(getApplicationContext(), SimpleActivity.class); 749 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 750 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 751 activityOptions.setLaunchDisplayId(displayId); 752 return (SimpleActivity) SystemUtil.runWithShellPermissionIdentity( 753 () -> InstrumentationRegistry.getInstrumentation() 754 .startActivitySync(intent, activityOptions.toBundle()), 755 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); 756 } 757 supportsActivitiesOnSecondaryDisplays()758 private boolean supportsActivitiesOnSecondaryDisplays() { 759 return mContext.getPackageManager().hasSystemFeature( 760 PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS); 761 } 762 supportsRotation()763 private boolean supportsRotation() { 764 final boolean supportsLandscape = mContext.getPackageManager().hasSystemFeature( 765 PackageManager.FEATURE_SCREEN_LANDSCAPE); 766 final boolean supportsPortrait = mContext.getPackageManager().hasSystemFeature( 767 PackageManager.FEATURE_SCREEN_PORTRAIT); 768 mWindowManagerStateHelper.computeState(); 769 final boolean isFixedToUserRotation = mWindowManagerStateHelper.isFixedToUserRotation(); 770 return (supportsLandscape && supportsPortrait && !isFixedToUserRotation) 771 || (!supportsLandscape && !supportsPortrait && !isFixedToUserRotation); 772 } 773 runOnUiThread(Runnable runnable)774 private void runOnUiThread(Runnable runnable) { 775 Runnable waiter = new Runnable() { 776 @Override 777 public void run() { 778 synchronized (this) { 779 notifyAll(); 780 } 781 } 782 }; 783 synchronized (waiter) { 784 mHandler.post(runnable); 785 mHandler.post(waiter); 786 try { 787 waiter.wait(TIMEOUT); 788 } catch (InterruptedException ex) { 789 } 790 } 791 } 792 findDisplay(Display[] displays, String name)793 private Display findDisplay(Display[] displays, String name) { 794 for (int i = 0; i < displays.length; i++) { 795 if (displays[i].getName().equals(name)) { 796 return displays[i]; 797 } 798 } 799 return null; 800 } 801 getExpectedPortraitRotation()802 private int getExpectedPortraitRotation() { 803 if (mContext.getResources().getBoolean(Resources.getSystem().getIdentifier( 804 "config_reverseDefaultRotation", "bool", "android"))) { 805 return Surface.ROTATION_90; 806 } else { 807 return Surface.ROTATION_270; 808 } 809 } 810 811 private final class DisplayChangeWaiter { 812 private static final int DISPLAY_CHANGE_TIMEOUT_SECS = 5; 813 814 private final Display mDisplay; 815 private int mCurrentRotation; 816 private int mCurrentState; 817 final CountDownLatch mRotationChangedLatch = new CountDownLatch(1); 818 final CountDownLatch mStateChangedLatch = new CountDownLatch(1); 819 820 private final DisplayManager.DisplayListener mListener = 821 new DisplayManager.DisplayListener() { 822 @Override 823 public void onDisplayAdded(int displayId) {} 824 825 @Override 826 public void onDisplayRemoved(int displayId) {} 827 828 @Override 829 public void onDisplayChanged(int displayId) { 830 if (mDisplay.getDisplayId() != displayId) { 831 return; 832 } 833 if (mCurrentRotation != mDisplay.getRotation()) { 834 mCurrentRotation = mDisplay.getRotation(); 835 mRotationChangedLatch.countDown(); 836 } 837 if (mCurrentState != mDisplay.getState()) { 838 mCurrentState = mDisplay.getState(); 839 mStateChangedLatch.countDown(); 840 } 841 } 842 }; 843 DisplayChangeWaiter(Display display)844 DisplayChangeWaiter(Display display) { 845 mDisplay = display; 846 mCurrentRotation = mDisplay.getRotation(); 847 mCurrentState = mDisplay.getState(); 848 Handler handler = new Handler(Looper.getMainLooper()); 849 mDisplayManager.registerDisplayListener(mListener, handler); 850 } 851 rotationChanged()852 boolean rotationChanged() throws Exception { 853 try { 854 return mRotationChangedLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS); 855 } finally { 856 mDisplayManager.unregisterDisplayListener(mListener); 857 } 858 } 859 stateChanged()860 boolean stateChanged() throws Exception { 861 try { 862 return mStateChangedLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS); 863 } finally { 864 mDisplayManager.unregisterDisplayListener(mListener); 865 } 866 } 867 } 868 869 private final class TestPresentation extends Presentation { 870 private final int mColor; 871 private final int mWindowFlags; 872 TestPresentation(Context context, Display display, int color, int windowFlags)873 public TestPresentation(Context context, Display display, 874 int color, int windowFlags) { 875 super(context, display); 876 mColor = color; 877 mWindowFlags = windowFlags; 878 } 879 880 @Override onCreate(Bundle savedInstanceState)881 protected void onCreate(Bundle savedInstanceState) { 882 super.onCreate(savedInstanceState); 883 884 setTitle(TAG); 885 getWindow().addFlags(mWindowFlags); 886 887 // Create a solid color image to use as the content of the presentation. 888 ImageView view = new ImageView(getContext()); 889 view.setImageDrawable(new ColorDrawable(mColor)); 890 view.setLayoutParams(new LayoutParams( 891 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 892 setContentView(view); 893 } 894 } 895 896 /** 897 * Watches for an image with a large amount of some particular solid color to be shown. 898 */ 899 private final class ImageListener 900 implements ImageReader.OnImageAvailableListener { 901 private int mColor = -1; 902 getColor()903 public int getColor() { 904 synchronized (this) { 905 return mColor; 906 } 907 } 908 waitForColor(int color, long timeoutMillis)909 public boolean waitForColor(int color, long timeoutMillis) { 910 long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis; 911 synchronized (this) { 912 while (mColor != color) { 913 long now = SystemClock.uptimeMillis(); 914 if (now >= timeoutTime) { 915 return false; 916 } 917 try { 918 wait(timeoutTime - now); 919 } catch (InterruptedException ex) { 920 } 921 } 922 return true; 923 } 924 } 925 926 @Override onImageAvailable(ImageReader reader)927 public void onImageAvailable(ImageReader reader) { 928 mImageReaderLock.lock(); 929 try { 930 if (reader != mImageReader) { 931 return; 932 } 933 934 Log.d(TAG, "New image available from virtual display."); 935 // Get the latest buffer 936 Image image = reader.acquireLatestImage(); 937 if (image != null) { 938 try { 939 // Scan for colors. 940 int color = scanImage(image); 941 synchronized (this) { 942 if (mColor != color) { 943 mColor = color; 944 notifyAll(); 945 } 946 } 947 } finally { 948 image.close(); 949 } 950 } 951 } finally { 952 mImageReaderLock.unlock(); 953 } 954 } 955 scanImage(Image image)956 private int scanImage(Image image) { 957 final Image.Plane plane = image.getPlanes()[0]; 958 final ByteBuffer buffer = plane.getBuffer(); 959 final int width = image.getWidth(); 960 final int height = image.getHeight(); 961 final int pixelStride = plane.getPixelStride(); 962 final int rowStride = plane.getRowStride(); 963 final int rowPadding = rowStride - pixelStride * width; 964 965 Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height 966 + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride); 967 968 int offset = 0; 969 int blackPixels = 0; 970 int bluePixels = 0; 971 int greenPixels = 0; 972 int otherPixels = 0; 973 for (int y = 0; y < height; y++) { 974 for (int x = 0; x < width; x++) { 975 int pixel = 0; 976 pixel |= (buffer.get(offset) & 0xff) << 16; // R 977 pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G 978 pixel |= (buffer.get(offset + 2) & 0xff); // B 979 pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A 980 if (pixel == Color.BLACK || pixel == 0) { 981 blackPixels += 1; 982 } else if (pixel == BLUEISH) { 983 bluePixels += 1; 984 } else if (pixel == GREENISH) { 985 greenPixels += 1; 986 } else { 987 otherPixels += 1; 988 if (otherPixels < 10) { 989 Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel)); 990 } 991 } 992 offset += pixelStride; 993 } 994 offset += rowPadding; 995 } 996 997 // Return a color if it represents more than one quarter of the pixels. 998 // We use this threshold in case the display is being letterboxed when 999 // mirroring so there might be large black bars on the sides, which is normal. 1000 Log.d(TAG, "- Pixels: " + blackPixels + " black, " 1001 + bluePixels + " blue, " 1002 + greenPixels + " green, " 1003 + otherPixels + " other"); 1004 final int threshold = width * height / 4; 1005 if (bluePixels > threshold) { 1006 Log.d(TAG, "- Reporting blue."); 1007 return BLUEISH; 1008 } 1009 if (greenPixels > threshold) { 1010 Log.d(TAG, "- Reporting green."); 1011 return GREENISH; 1012 } 1013 if (blackPixels > threshold) { 1014 Log.d(TAG, "- Reporting black."); 1015 return Color.BLACK; 1016 } 1017 Log.d(TAG, "- Reporting other."); 1018 return -1; 1019 } 1020 } 1021 } 1022 1023