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.assertTrue; 29 import static org.junit.Assert.fail; 30 import static org.junit.Assume.assumeTrue; 31 32 import android.Manifest; 33 import android.app.ActivityOptions; 34 import android.app.Presentation; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.pm.ActivityInfo; 38 import android.content.pm.PackageManager; 39 import android.content.res.Resources; 40 import android.graphics.Color; 41 import android.graphics.PixelFormat; 42 import android.graphics.Point; 43 import android.graphics.drawable.ColorDrawable; 44 import android.hardware.display.DisplayManager; 45 import android.hardware.display.VirtualDisplay; 46 import android.hardware.display.VirtualDisplayConfig; 47 import android.media.Image; 48 import android.media.ImageReader; 49 import android.os.Bundle; 50 import android.os.Handler; 51 import android.os.HandlerThread; 52 import android.os.Looper; 53 import android.os.SystemClock; 54 import android.platform.test.annotations.AppModeSdkSandbox; 55 import android.platform.test.annotations.AsbSecurityTest; 56 import android.provider.Settings; 57 import android.server.wm.IgnoreOrientationRequestSession; 58 import android.util.DisplayMetrics; 59 import android.util.Log; 60 import android.view.Display; 61 import android.view.Surface; 62 import android.view.ViewGroup.LayoutParams; 63 import android.widget.ImageView; 64 65 66 import androidx.test.ext.junit.runners.AndroidJUnit4; 67 import androidx.test.platform.app.InstrumentationRegistry; 68 69 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 70 import com.android.compatibility.common.util.DisplayStateManager; 71 import com.android.compatibility.common.util.SettingsStateKeeperRule; 72 import com.android.compatibility.common.util.StateKeeperRule; 73 import com.android.compatibility.common.util.SystemUtil; 74 import com.android.compatibility.common.util.UserSettings.Namespace; 75 76 import org.junit.After; 77 import org.junit.Before; 78 import org.junit.ClassRule; 79 import org.junit.Rule; 80 import org.junit.Test; 81 import org.junit.runner.RunWith; 82 83 import java.nio.ByteBuffer; 84 import java.util.concurrent.CountDownLatch; 85 import java.util.concurrent.TimeUnit; 86 import java.util.concurrent.locks.Lock; 87 import java.util.concurrent.locks.ReentrantLock; 88 89 /** 90 * Tests that applications can create virtual displays and present content on them. 91 * 92 * This CTS test is unable to test public virtual displays since special permissions 93 * are required. See also framework VirtualDisplayTest unit tests. 94 */ 95 @RunWith(AndroidJUnit4.class) 96 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") 97 public class VirtualDisplayTest { 98 private static final String TAG = "VirtualDisplayTest"; 99 100 private static final String NAME = TAG; 101 private static final int WIDTH = 720; 102 private static final int HEIGHT = 480; 103 private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM; 104 private static final float REQUESTED_REFRESH_RATE = 30.0f; 105 private static final int TIMEOUT = 40000; 106 107 // Colors that we use as a signal to determine whether some desired content was 108 // drawn. The colors themselves doesn't matter but we choose them to have with distinct 109 // values for each color channel so as to detect possible RGBA vs. BGRA buffer format issues. 110 // We should only observe RGBA buffers but some graphics drivers might incorrectly 111 // deliver BGRA buffers to virtual displays instead. 112 private static final int BLUEISH = 0xff1122ee; 113 private static final int GREENISH = 0xff33dd44; 114 115 private Context mContext; 116 private DisplayManager mDisplayManager; 117 private Handler mHandler; 118 private final Lock mImageReaderLock = new ReentrantLock(true /*fair*/); 119 private ImageReader mImageReader; 120 private Surface mSurface; 121 private ImageListener mImageListener; 122 private HandlerThread mCheckThread; 123 private Handler mCheckHandler; 124 125 @Rule(order = 0) 126 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( 127 InstrumentationRegistry.getInstrumentation().getUiAutomation(), 128 Manifest.permission.ADD_TRUSTED_DISPLAY, 129 Manifest.permission.WRITE_SECURE_SETTINGS); 130 131 @ClassRule 132 public static final SettingsStateKeeperRule mAreUserDisabledHdrFormatsAllowedSettingsKeeper = 133 new SettingsStateKeeperRule( 134 InstrumentationRegistry.getInstrumentation().getTargetContext(), 135 Namespace.GLOBAL, Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED); 136 137 @ClassRule 138 public static final SettingsStateKeeperRule mUserDisabledHdrFormatsSettingsKeeper = 139 new SettingsStateKeeperRule( 140 InstrumentationRegistry.getInstrumentation().getTargetContext(), 141 Namespace.GLOBAL, Settings.Global.USER_DISABLED_HDR_FORMATS); 142 143 @Rule(order = 1) 144 public StateKeeperRule<DisplayStateManager.DisplayState> mDisplayManagerStateKeeper = 145 new StateKeeperRule<>(new DisplayStateManager( 146 InstrumentationRegistry.getInstrumentation().getTargetContext())); 147 148 @Before setUp()149 public void setUp() throws Exception { 150 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 151 mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE); 152 mHandler = new Handler(Looper.getMainLooper()); 153 mImageListener = new ImageListener(); 154 // thread for image checking 155 mCheckThread = new HandlerThread("TestHandler"); 156 mCheckThread.start(); 157 mCheckHandler = new Handler(mCheckThread.getLooper()); 158 159 mImageReaderLock.lock(); 160 try { 161 mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2); 162 mImageReader.setOnImageAvailableListener(mImageListener, mCheckHandler); 163 mSurface = mImageReader.getSurface(); 164 } finally { 165 mImageReaderLock.unlock(); 166 } 167 } 168 169 @After tearDown()170 public void tearDown() throws Exception { 171 mImageReaderLock.lock(); 172 try { 173 mImageReader.close(); 174 mImageReader = null; 175 mSurface = null; 176 } finally { 177 mImageReaderLock.unlock(); 178 } 179 mCheckThread.quit(); 180 } 181 182 /** 183 * Ensures that an application can create a private virtual display and show 184 * its own windows on it. 185 */ 186 @Test 187 @AsbSecurityTest(cveBugId = 141745510) testPrivateVirtualDisplay()188 public void testPrivateVirtualDisplay() throws Exception { 189 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 190 WIDTH, HEIGHT, DENSITY, mSurface, 0); 191 assertNotNull("virtual display must not be null", virtualDisplay); 192 193 Display display = virtualDisplay.getDisplay(); 194 try { 195 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 196 assertEquals(mSurface, virtualDisplay.getSurface()); 197 198 // Show a private presentation on the display. 199 assertDisplayCanShowPresentation("private presentation window", 200 display, BLUEISH, 0); 201 } finally { 202 virtualDisplay.release(); 203 } 204 assertDisplayUnregistered(display); 205 } 206 207 /** 208 * Ensures that an application can create a private presentation virtual display and show 209 * its own windows on it. 210 */ 211 @Test 212 @AsbSecurityTest(cveBugId = 141745510) testPrivatePresentationVirtualDisplay()213 public void testPrivatePresentationVirtualDisplay() throws Exception { 214 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 215 WIDTH, HEIGHT, DENSITY, mSurface, 216 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); 217 assertNotNull("virtual display must not be null", virtualDisplay); 218 219 Display display = virtualDisplay.getDisplay(); 220 try { 221 assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION); 222 assertEquals(mSurface, virtualDisplay.getSurface()); 223 224 // Show a private presentation on the display. 225 assertDisplayCanShowPresentation("private presentation window", 226 display, BLUEISH, 0); 227 } finally { 228 virtualDisplay.release(); 229 } 230 assertDisplayUnregistered(display); 231 } 232 233 /** 234 * Ensures that an application can create a private virtual display and show 235 * its own windows on it where the surface is attached or detached dynamically. 236 */ 237 @Test 238 @AsbSecurityTest(cveBugId = 141745510) testPrivateVirtualDisplayWithDynamicSurface()239 public void testPrivateVirtualDisplayWithDynamicSurface() throws Exception { 240 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 241 WIDTH, HEIGHT, DENSITY, null, 0); 242 assertNotNull("virtual display must not be null", virtualDisplay); 243 244 Display display = virtualDisplay.getDisplay(); 245 try { 246 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 247 assertNull(virtualDisplay.getSurface()); 248 249 // Attach the surface. 250 virtualDisplay.setSurface(mSurface); 251 assertEquals(mSurface, virtualDisplay.getSurface()); 252 253 // Show a private presentation on the display. 254 assertDisplayCanShowPresentation("private presentation window", 255 display, BLUEISH, 0); 256 257 // Detach the surface. 258 virtualDisplay.setSurface(null); 259 assertNull(virtualDisplay.getSurface()); 260 } finally { 261 virtualDisplay.release(); 262 } 263 assertDisplayUnregistered(display); 264 } 265 266 /** 267 * Ensures that {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} will 268 * be clear if an application creates an virtual display without the 269 * flag {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED}. 270 */ 271 @Test testUntrustedSysDecorVirtualDisplay()272 public void testUntrustedSysDecorVirtualDisplay() throws Exception { 273 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 274 WIDTH, HEIGHT, DENSITY, mSurface, 275 VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS); 276 assertNotNull("virtual display must not be null", virtualDisplay); 277 278 Display display = virtualDisplay.getDisplay(); 279 try { 280 // Verify that the created virtual display doesn't have flags 281 // FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS. 282 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 283 assertEquals(mSurface, virtualDisplay.getSurface()); 284 285 // Show a private presentation on the display. 286 assertDisplayCanShowPresentation("private presentation window", 287 display, BLUEISH, 0); 288 } finally { 289 virtualDisplay.release(); 290 } 291 assertDisplayUnregistered(display); 292 } 293 294 /** 295 * Ensures that throws {@link SecurityException} when an application creates a trusted virtual 296 * display without holding the permission {@code ADD_TRUSTED_DISPLAY}. 297 */ 298 @Test testTrustedVirtualDisplay()299 public void testTrustedVirtualDisplay() throws Exception { 300 InstrumentationRegistry.getInstrumentation().getUiAutomation() 301 .dropShellPermissionIdentity(); 302 303 try { 304 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 305 WIDTH, HEIGHT, DENSITY, mSurface, VIRTUAL_DISPLAY_FLAG_TRUSTED); 306 } catch (SecurityException e) { 307 // Expected. 308 return; 309 } 310 fail("SecurityException must be thrown if a trusted virtual display is created without" 311 + "holding the permission ADD_TRUSTED_DISPLAY."); 312 } 313 314 /** 315 * Ensures that an application can create a private virtual display with a requested 316 * refresh rate and show its own windows on it. 317 */ 318 @Test testVirtualDisplayWithRequestedRefreshRate()319 public void testVirtualDisplayWithRequestedRefreshRate() throws Exception { 320 VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(NAME, WIDTH, HEIGHT, DENSITY) 321 .setSurface(mSurface) 322 .setRequestedRefreshRate(REQUESTED_REFRESH_RATE) 323 .build(); 324 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(config); 325 assertNotNull("virtual display must not be null", virtualDisplay); 326 Display display = virtualDisplay.getDisplay(); 327 try { 328 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 329 assertEquals(mSurface, virtualDisplay.getSurface()); 330 331 assertEquals(display.getRefreshRate(), REQUESTED_REFRESH_RATE, 0.1f); 332 } finally { 333 virtualDisplay.release(); 334 } 335 assertDisplayUnregistered(display); 336 } 337 338 @Test testVirtualDisplayRotatesWithContent()339 public void testVirtualDisplayRotatesWithContent() throws Exception { 340 assumeTrue(supportsActivitiesOnSecondaryDisplays()); 341 assumeTrue(supportsRotation()); 342 343 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 344 WIDTH, HEIGHT, DENSITY, mSurface, 345 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 346 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 347 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY 348 | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT); 349 assertNotNull("virtual display must not be null", virtualDisplay); 350 351 Display display = virtualDisplay.getDisplay(); 352 assertEquals(Surface.ROTATION_0, display.getRotation()); 353 SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId()); 354 try (IgnoreOrientationRequestSession unused = 355 new IgnoreOrientationRequestSession(/* enable= */ false)) { 356 { 357 RotationChangeWaiter waiter = new RotationChangeWaiter(display); 358 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 359 assertTrue(waiter.rotationChanged()); 360 assertEquals(getExpectedPortraitRotation(), display.getRotation()); 361 } 362 { 363 RotationChangeWaiter waiter = new RotationChangeWaiter(display); 364 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 365 assertTrue(waiter.rotationChanged()); 366 assertEquals(Surface.ROTATION_0, display.getRotation()); 367 } 368 } finally { 369 virtualDisplay.release(); 370 } 371 assertDisplayUnregistered(display); 372 } 373 374 @Test testVirtualDisplayDoesNotRotateWithContent()375 public void testVirtualDisplayDoesNotRotateWithContent() throws Exception { 376 assumeTrue(supportsActivitiesOnSecondaryDisplays()); 377 378 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 379 WIDTH, HEIGHT, DENSITY, mSurface, 380 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 381 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 382 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 383 assertNotNull("virtual display must not be null", virtualDisplay); 384 385 Display display = virtualDisplay.getDisplay(); 386 assertEquals(Surface.ROTATION_0, display.getRotation()); 387 SimpleActivity activity = launchTestActivityOnDisplay(display.getDisplayId()); 388 try { 389 { 390 RotationChangeWaiter waiter = new RotationChangeWaiter(display); 391 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 392 assertFalse(waiter.rotationChanged()); 393 assertEquals(Surface.ROTATION_0, display.getRotation()); 394 } 395 { 396 RotationChangeWaiter waiter = new RotationChangeWaiter(display); 397 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 398 assertFalse(waiter.rotationChanged()); 399 assertEquals(Surface.ROTATION_0, display.getRotation()); 400 } 401 } finally { 402 virtualDisplay.release(); 403 } 404 assertDisplayUnregistered(display); 405 } 406 407 @Test testHdrApiMethods()408 public void testHdrApiMethods() { 409 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 410 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0); 411 try { 412 assertFalse(virtualDisplay.getDisplay().isHdr()); 413 assertNull(virtualDisplay.getDisplay().getHdrCapabilities()); 414 } finally { 415 virtualDisplay.release(); 416 } 417 } 418 419 @Test testGetHdrCapabilitiesWithUserDisabledFormats()420 public void testGetHdrCapabilitiesWithUserDisabledFormats() { 421 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 422 WIDTH, HEIGHT, DENSITY, mSurface, /*flags*/ 0); 423 mDisplayManager.setAreUserDisabledHdrTypesAllowed(false); 424 int[] userDisabledHdrTypes = { 425 Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION, 426 Display.HdrCapabilities.HDR_TYPE_HLG}; 427 mDisplayManager.setUserDisabledHdrTypes(userDisabledHdrTypes); 428 429 try { 430 assertFalse(virtualDisplay.getDisplay().isHdr()); 431 assertNull(virtualDisplay.getDisplay().getHdrCapabilities()); 432 } finally { 433 virtualDisplay.release(); 434 } 435 } 436 assertDisplayRegistered(Display display, int flags)437 private void assertDisplayRegistered(Display display, int flags) { 438 assertNotNull("display object must not be null", display); 439 assertTrue("display must be valid", display.isValid()); 440 assertTrue("display id must be unique", 441 display.getDisplayId() != Display.DEFAULT_DISPLAY); 442 assertEquals("display must have correct flags", flags, display.getFlags()); 443 assertEquals("display name must match supplied name", NAME, display.getName()); 444 Point size = new Point(); 445 display.getSize(size); 446 assertEquals("display width must match supplied width", WIDTH, size.x); 447 assertEquals("display height must match supplied height", HEIGHT, size.y); 448 assertEquals("display rotation must be 0", 449 Surface.ROTATION_0, display.getRotation()); 450 assertNotNull("display must be registered", 451 findDisplay(mDisplayManager.getDisplays(), NAME)); 452 453 if ((flags & Display.FLAG_PRESENTATION) != 0) { 454 assertNotNull("display must be registered as a presentation display", 455 findDisplay(mDisplayManager.getDisplays( 456 DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); 457 } else { 458 assertNull("display must not be registered as a presentation display", 459 findDisplay(mDisplayManager.getDisplays( 460 DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); 461 } 462 } 463 assertDisplayUnregistered(Display display)464 private void assertDisplayUnregistered(Display display) { 465 assertNull("display must no longer be registered after being removed", 466 findDisplay(mDisplayManager.getDisplays(), NAME)); 467 assertFalse("display must no longer be valid", display.isValid()); 468 } 469 assertDisplayCanShowPresentation(String message, final Display display, final int color, final int windowFlags)470 private void assertDisplayCanShowPresentation(String message, final Display display, 471 final int color, final int windowFlags) { 472 // At this point, we should not have seen any blue. 473 assertTrue(message + ": display should not show content before window is shown", 474 mImageListener.getColor() != color); 475 476 final TestPresentation[] presentation = new TestPresentation[1]; 477 try { 478 // Show the presentation. 479 runOnUiThread(new Runnable() { 480 @Override 481 public void run() { 482 presentation[0] = new TestPresentation(mContext, display, 483 color, windowFlags); 484 presentation[0].show(); 485 } 486 }); 487 488 // Wait for the blue to be seen. 489 assertTrue(message + ": display should show content after window is shown", 490 mImageListener.waitForColor(color, TIMEOUT)); 491 } finally { 492 if (presentation[0] != null) { 493 runOnUiThread(new Runnable() { 494 @Override 495 public void run() { 496 presentation[0].dismiss(); 497 } 498 }); 499 } 500 } 501 } 502 launchTestActivityOnDisplay(int displayId)503 private SimpleActivity launchTestActivityOnDisplay(int displayId) { 504 Intent intent = new Intent(getApplicationContext(), SimpleActivity.class); 505 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 506 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 507 activityOptions.setLaunchDisplayId(displayId); 508 return (SimpleActivity) SystemUtil.runWithShellPermissionIdentity( 509 () -> InstrumentationRegistry.getInstrumentation() 510 .startActivitySync(intent, activityOptions.toBundle()), 511 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); 512 } 513 supportsActivitiesOnSecondaryDisplays()514 private boolean supportsActivitiesOnSecondaryDisplays() { 515 return mContext.getPackageManager().hasSystemFeature( 516 PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS); 517 } 518 supportsRotation()519 private boolean supportsRotation() { 520 final boolean supportsLandscape = mContext.getPackageManager().hasSystemFeature( 521 PackageManager.FEATURE_SCREEN_LANDSCAPE); 522 final boolean supportsPortrait = mContext.getPackageManager().hasSystemFeature( 523 PackageManager.FEATURE_SCREEN_PORTRAIT); 524 return (supportsLandscape && supportsPortrait) 525 || (!supportsLandscape && !supportsPortrait); 526 } 527 runOnUiThread(Runnable runnable)528 private void runOnUiThread(Runnable runnable) { 529 Runnable waiter = new Runnable() { 530 @Override 531 public void run() { 532 synchronized (this) { 533 notifyAll(); 534 } 535 } 536 }; 537 synchronized (waiter) { 538 mHandler.post(runnable); 539 mHandler.post(waiter); 540 try { 541 waiter.wait(TIMEOUT); 542 } catch (InterruptedException ex) { 543 } 544 } 545 } 546 findDisplay(Display[] displays, String name)547 private Display findDisplay(Display[] displays, String name) { 548 for (int i = 0; i < displays.length; i++) { 549 if (displays[i].getName().equals(name)) { 550 return displays[i]; 551 } 552 } 553 return null; 554 } 555 getExpectedPortraitRotation()556 private int getExpectedPortraitRotation() { 557 if (mContext.getResources().getBoolean(Resources.getSystem().getIdentifier( 558 "config_reverseDefaultRotation", "bool", "android"))) { 559 return Surface.ROTATION_90; 560 } else { 561 return Surface.ROTATION_270; 562 } 563 } 564 565 private final class RotationChangeWaiter { 566 private static final int DISPLAY_CHANGE_TIMEOUT_SECS = 3; 567 568 private final Display mDisplay; 569 private int mCurrentRotation; 570 final CountDownLatch mRotationChangedLatch = new CountDownLatch(1); 571 572 private final DisplayManager.DisplayListener mListener = 573 new DisplayManager.DisplayListener() { 574 @Override 575 public void onDisplayAdded(int displayId) {} 576 577 @Override 578 public void onDisplayRemoved(int displayId) {} 579 580 @Override 581 public void onDisplayChanged(int displayId) { 582 if (mDisplay.getDisplayId() == displayId 583 && mCurrentRotation != mDisplay.getRotation()) { 584 mCurrentRotation = mDisplay.getRotation(); 585 mRotationChangedLatch.countDown(); 586 } 587 } 588 }; 589 RotationChangeWaiter(Display display)590 RotationChangeWaiter(Display display) { 591 mDisplay = display; 592 mCurrentRotation = mDisplay.getRotation(); 593 Handler handler = new Handler(Looper.getMainLooper()); 594 mDisplayManager.registerDisplayListener(mListener, handler); 595 } 596 rotationChanged()597 boolean rotationChanged() throws Exception { 598 try { 599 return mRotationChangedLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS); 600 } finally { 601 mDisplayManager.unregisterDisplayListener(mListener); 602 } 603 } 604 } 605 606 private final class TestPresentation extends Presentation { 607 private final int mColor; 608 private final int mWindowFlags; 609 TestPresentation(Context context, Display display, int color, int windowFlags)610 public TestPresentation(Context context, Display display, 611 int color, int windowFlags) { 612 super(context, display); 613 mColor = color; 614 mWindowFlags = windowFlags; 615 } 616 617 @Override onCreate(Bundle savedInstanceState)618 protected void onCreate(Bundle savedInstanceState) { 619 super.onCreate(savedInstanceState); 620 621 setTitle(TAG); 622 getWindow().addFlags(mWindowFlags); 623 624 // Create a solid color image to use as the content of the presentation. 625 ImageView view = new ImageView(getContext()); 626 view.setImageDrawable(new ColorDrawable(mColor)); 627 view.setLayoutParams(new LayoutParams( 628 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 629 setContentView(view); 630 } 631 } 632 633 /** 634 * Watches for an image with a large amount of some particular solid color to be shown. 635 */ 636 private final class ImageListener 637 implements ImageReader.OnImageAvailableListener { 638 private int mColor = -1; 639 getColor()640 public int getColor() { 641 synchronized (this) { 642 return mColor; 643 } 644 } 645 waitForColor(int color, long timeoutMillis)646 public boolean waitForColor(int color, long timeoutMillis) { 647 long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis; 648 synchronized (this) { 649 while (mColor != color) { 650 long now = SystemClock.uptimeMillis(); 651 if (now >= timeoutTime) { 652 return false; 653 } 654 try { 655 wait(timeoutTime - now); 656 } catch (InterruptedException ex) { 657 } 658 } 659 return true; 660 } 661 } 662 663 @Override onImageAvailable(ImageReader reader)664 public void onImageAvailable(ImageReader reader) { 665 mImageReaderLock.lock(); 666 try { 667 if (reader != mImageReader) { 668 return; 669 } 670 671 Log.d(TAG, "New image available from virtual display."); 672 // Get the latest buffer 673 Image image = reader.acquireLatestImage(); 674 if (image != null) { 675 try { 676 // Scan for colors. 677 int color = scanImage(image); 678 synchronized (this) { 679 if (mColor != color) { 680 mColor = color; 681 notifyAll(); 682 } 683 } 684 } finally { 685 image.close(); 686 } 687 } 688 } finally { 689 mImageReaderLock.unlock(); 690 } 691 } 692 scanImage(Image image)693 private int scanImage(Image image) { 694 final Image.Plane plane = image.getPlanes()[0]; 695 final ByteBuffer buffer = plane.getBuffer(); 696 final int width = image.getWidth(); 697 final int height = image.getHeight(); 698 final int pixelStride = plane.getPixelStride(); 699 final int rowStride = plane.getRowStride(); 700 final int rowPadding = rowStride - pixelStride * width; 701 702 Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height 703 + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride); 704 705 int offset = 0; 706 int blackPixels = 0; 707 int bluePixels = 0; 708 int greenPixels = 0; 709 int otherPixels = 0; 710 for (int y = 0; y < height; y++) { 711 for (int x = 0; x < width; x++) { 712 int pixel = 0; 713 pixel |= (buffer.get(offset) & 0xff) << 16; // R 714 pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G 715 pixel |= (buffer.get(offset + 2) & 0xff); // B 716 pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A 717 if (pixel == Color.BLACK || pixel == 0) { 718 blackPixels += 1; 719 } else if (pixel == BLUEISH) { 720 bluePixels += 1; 721 } else if (pixel == GREENISH) { 722 greenPixels += 1; 723 } else { 724 otherPixels += 1; 725 if (otherPixels < 10) { 726 Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel)); 727 } 728 } 729 offset += pixelStride; 730 } 731 offset += rowPadding; 732 } 733 734 // Return a color if it represents more than one quarter of the pixels. 735 // We use this threshold in case the display is being letterboxed when 736 // mirroring so there might be large black bars on the sides, which is normal. 737 Log.d(TAG, "- Pixels: " + blackPixels + " black, " 738 + bluePixels + " blue, " 739 + greenPixels + " green, " 740 + otherPixels + " other"); 741 final int threshold = width * height / 4; 742 if (bluePixels > threshold) { 743 Log.d(TAG, "- Reporting blue."); 744 return BLUEISH; 745 } 746 if (greenPixels > threshold) { 747 Log.d(TAG, "- Reporting green."); 748 return GREENISH; 749 } 750 if (blackPixels > threshold) { 751 Log.d(TAG, "- Reporting black."); 752 return Color.BLACK; 753 } 754 Log.d(TAG, "- Reporting other."); 755 return -1; 756 } 757 } 758 } 759 760