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.hardware.display; 18 19 import android.app.Presentation; 20 import android.content.Context; 21 import android.graphics.Color; 22 import android.graphics.PixelFormat; 23 import android.graphics.Point; 24 import android.graphics.drawable.ColorDrawable; 25 import android.hardware.display.DisplayManager; 26 import android.hardware.display.VirtualDisplay; 27 import android.media.Image; 28 import android.media.ImageReader; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.SystemClock; 33 import android.test.AndroidTestCase; 34 import android.util.DisplayMetrics; 35 import android.util.Log; 36 import android.view.Display; 37 import android.view.Surface; 38 import android.view.ViewGroup.LayoutParams; 39 import android.view.WindowManager; 40 import android.widget.ImageView; 41 42 import java.nio.ByteBuffer; 43 import java.util.concurrent.locks.Lock; 44 import java.util.concurrent.locks.ReentrantLock; 45 46 /** 47 * Tests that applications can create virtual displays and present content on them. 48 * 49 * Contains additional tests that cannot be included in CTS because they require 50 * system permissions. See also the CTS version of VirtualDisplayTest. 51 */ 52 public class VirtualDisplayTest extends AndroidTestCase { 53 private static final String TAG = "VirtualDisplayTest"; 54 55 private static final String NAME = TAG; 56 private static final int WIDTH = 720; 57 private static final int HEIGHT = 480; 58 private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM; 59 private static final int TIMEOUT = 10000; 60 61 // Colors that we use as a signal to determine whether some desired content was 62 // drawn. The colors themselves doesn't matter but we choose them to have with distinct 63 // values for each color channel so as to detect possible RGBA vs. BGRA buffer format issues. 64 // We should only observe RGBA buffers but some graphics drivers might incorrectly 65 // deliver BGRA buffers to virtual displays instead. 66 private static final int BLUEISH = 0xff1122ee; 67 private static final int GREENISH = 0xff33dd44; 68 69 private DisplayManager mDisplayManager; 70 private Handler mHandler; 71 private final Lock mImageReaderLock = new ReentrantLock(true /*fair*/); 72 private ImageReader mImageReader; 73 private Surface mSurface; 74 private ImageListener mImageListener; 75 76 @Override 77 protected void setUp() throws Exception { 78 super.setUp(); 79 80 mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE); 81 mHandler = new Handler(Looper.getMainLooper()); 82 mImageListener = new ImageListener(); 83 84 mImageReaderLock.lock(); 85 try { 86 mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2); 87 mImageReader.setOnImageAvailableListener(mImageListener, mHandler); 88 mSurface = mImageReader.getSurface(); 89 } finally { 90 mImageReaderLock.unlock(); 91 } 92 } 93 94 @Override 95 protected void tearDown() throws Exception { 96 super.tearDown(); 97 98 mImageReaderLock.lock(); 99 try { 100 mImageReader.close(); 101 mImageReader = null; 102 mSurface = null; 103 } finally { 104 mImageReaderLock.unlock(); 105 } 106 } 107 108 /** 109 * Ensures that an application can create a private virtual display and show 110 * its own windows on it. 111 */ 112 public void testPrivateVirtualDisplay() throws Exception { 113 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 114 WIDTH, HEIGHT, DENSITY, mSurface, 0); 115 assertNotNull("virtual display must not be null", virtualDisplay); 116 117 Display display = virtualDisplay.getDisplay(); 118 try { 119 assertDisplayRegistered(display, Display.FLAG_PRIVATE); 120 121 // Show a private presentation on the display. 122 assertDisplayCanShowPresentation("private presentation window", 123 display, BLUEISH, 124 WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0); 125 } finally { 126 virtualDisplay.release(); 127 } 128 assertDisplayUnregistered(display); 129 } 130 131 /** 132 * Ensures that an application can create a private presentation virtual display and show 133 * its own windows on it. 134 */ 135 public void testPrivatePresentationVirtualDisplay() throws Exception { 136 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 137 WIDTH, HEIGHT, DENSITY, mSurface, 138 DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); 139 assertNotNull("virtual display must not be null", virtualDisplay); 140 141 Display display = virtualDisplay.getDisplay(); 142 try { 143 assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION); 144 145 // Show a private presentation on the display. 146 assertDisplayCanShowPresentation("private presentation window", 147 display, BLUEISH, 148 WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0); 149 } finally { 150 virtualDisplay.release(); 151 } 152 assertDisplayUnregistered(display); 153 } 154 155 /** 156 * Ensures that an application can create a public virtual display and show 157 * its own windows on it. This test requires the CAPTURE_VIDEO_OUTPUT permission. 158 * 159 * Because this test does not have an activity token, we use the TOAST window 160 * type to create the window. Another choice might be SYSTEM_ALERT_WINDOW but 161 * that requires a permission. 162 */ 163 public void testPublicPresentationVirtualDisplay() throws Exception { 164 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 165 WIDTH, HEIGHT, DENSITY, mSurface, 166 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 167 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); 168 assertNotNull("virtual display must not be null", virtualDisplay); 169 170 Display display = virtualDisplay.getDisplay(); 171 try { 172 assertDisplayRegistered(display, Display.FLAG_PRESENTATION); 173 174 // Mirroring case. 175 // Show a window on the default display. It should be mirrored to the 176 // virtual display automatically. 177 Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); 178 assertDisplayCanShowPresentation("mirrored window", 179 defaultDisplay, GREENISH, 180 WindowManager.LayoutParams.TYPE_TOAST, 0); 181 182 // Mirroring case with secure window (but display is not secure). 183 // Show a window on the default display. It should be replaced with black on 184 // the virtual display. 185 assertDisplayCanShowPresentation("mirrored secure window on non-secure display", 186 defaultDisplay, Color.BLACK, 187 WindowManager.LayoutParams.TYPE_TOAST, 188 WindowManager.LayoutParams.FLAG_SECURE); 189 190 // Presentation case. 191 // Show a normal presentation on the display. 192 assertDisplayCanShowPresentation("presentation window", 193 display, BLUEISH, 194 WindowManager.LayoutParams.TYPE_TOAST, 0); 195 196 // Presentation case with secure window (but display is not secure). 197 // Show a normal presentation on the display. It should be replaced with black. 198 assertDisplayCanShowPresentation("secure presentation window on non-secure display", 199 display, Color.BLACK, 200 WindowManager.LayoutParams.TYPE_TOAST, 201 WindowManager.LayoutParams.FLAG_SECURE); 202 } finally { 203 virtualDisplay.release(); 204 } 205 assertDisplayUnregistered(display); 206 } 207 208 /** 209 * Ensures that an application can create a secure public virtual display and show 210 * its own windows on it. This test requires the CAPTURE_SECURE_VIDEO_OUTPUT permission. 211 * 212 * Because this test does not have an activity token, we use the TOAST window 213 * type to create the window. Another choice might be SYSTEM_ALERT_WINDOW but 214 * that requires a permission. 215 */ 216 public void testSecurePublicPresentationVirtualDisplay() throws Exception { 217 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, 218 WIDTH, HEIGHT, DENSITY, mSurface, 219 DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE 220 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 221 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); 222 assertNotNull("virtual display must not be null", virtualDisplay); 223 224 Display display = virtualDisplay.getDisplay(); 225 try { 226 assertDisplayRegistered(display, Display.FLAG_PRESENTATION | Display.FLAG_SECURE); 227 228 // Mirroring case with secure window (and display is secure). 229 // Show a window on the default display. It should be mirrored to the 230 // virtual display automatically. 231 Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); 232 assertDisplayCanShowPresentation("mirrored secure window on secure display", 233 defaultDisplay, GREENISH, 234 WindowManager.LayoutParams.TYPE_TOAST, 235 WindowManager.LayoutParams.FLAG_SECURE); 236 237 // Presentation case with secure window (and display is secure). 238 // Show a normal presentation on the display. 239 assertDisplayCanShowPresentation("secure presentation window on secure display", 240 display, BLUEISH, 241 WindowManager.LayoutParams.TYPE_TOAST, 242 WindowManager.LayoutParams.FLAG_SECURE); 243 } finally { 244 virtualDisplay.release(); 245 } 246 assertDisplayUnregistered(display); 247 } 248 249 private void assertDisplayRegistered(Display display, int flags) { 250 assertNotNull("display object must not be null", display); 251 assertTrue("display must be valid", display.isValid()); 252 assertTrue("display id must be unique", 253 display.getDisplayId() != Display.DEFAULT_DISPLAY); 254 assertEquals("display must have correct flags", flags, display.getFlags()); 255 assertEquals("display name must match supplied name", NAME, display.getName()); 256 Point size = new Point(); 257 display.getSize(size); 258 assertEquals("display width must match supplied width", WIDTH, size.x); 259 assertEquals("display height must match supplied height", HEIGHT, size.y); 260 assertEquals("display rotation must be 0", 261 Surface.ROTATION_0, display.getRotation()); 262 assertNotNull("display must be registered", 263 findDisplay(mDisplayManager.getDisplays(), NAME)); 264 265 if ((flags & Display.FLAG_PRESENTATION) != 0) { 266 assertNotNull("display must be registered as a presentation display", 267 findDisplay(mDisplayManager.getDisplays( 268 DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); 269 } else { 270 assertNull("display must not be registered as a presentation display", 271 findDisplay(mDisplayManager.getDisplays( 272 DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME)); 273 } 274 } 275 276 private void assertDisplayUnregistered(Display display) { 277 assertNull("display must no longer be registered after being removed", 278 findDisplay(mDisplayManager.getDisplays(), NAME)); 279 assertFalse("display must no longer be valid", display.isValid()); 280 } 281 282 private void assertDisplayCanShowPresentation(String message, final Display display, 283 final int color, final int windowType, final int windowFlags) { 284 // At this point, we should not have seen any blue. 285 assertTrue(message + ": display should not show content before window is shown", 286 mImageListener.getColor() != color); 287 288 final TestPresentation[] presentation = new TestPresentation[1]; 289 try { 290 // Show the presentation. 291 runOnUiThread(new Runnable() { 292 @Override 293 public void run() { 294 presentation[0] = new TestPresentation(getContext(), display, 295 color, windowType, windowFlags); 296 presentation[0].show(); 297 } 298 }); 299 300 // Wait for the blue to be seen. 301 assertTrue(message + ": display should show content after window is shown", 302 mImageListener.waitForColor(color, TIMEOUT)); 303 } finally { 304 if (presentation[0] != null) { 305 runOnUiThread(new Runnable() { 306 @Override 307 public void run() { 308 presentation[0].dismiss(); 309 } 310 }); 311 } 312 } 313 } 314 315 private void runOnUiThread(Runnable runnable) { 316 Runnable waiter = new Runnable() { 317 @Override 318 public void run() { 319 synchronized (this) { 320 notifyAll(); 321 } 322 } 323 }; 324 synchronized (waiter) { 325 mHandler.post(runnable); 326 mHandler.post(waiter); 327 try { 328 waiter.wait(TIMEOUT); 329 } catch (InterruptedException ex) { 330 } 331 } 332 } 333 334 private Display findDisplay(Display[] displays, String name) { 335 for (int i = 0; i < displays.length; i++) { 336 if (displays[i].getName().equals(name)) { 337 return displays[i]; 338 } 339 } 340 return null; 341 } 342 343 private final class TestPresentation extends Presentation { 344 private final int mColor; 345 private final int mWindowType; 346 private final int mWindowFlags; 347 348 public TestPresentation(Context context, Display display, 349 int color, int windowType, int windowFlags) { 350 super(context, display); 351 mColor = color; 352 mWindowType = windowType; 353 mWindowFlags = windowFlags; 354 } 355 356 @Override 357 protected void onCreate(Bundle savedInstanceState) { 358 super.onCreate(savedInstanceState); 359 360 setTitle(TAG); 361 getWindow().setType(mWindowType); 362 getWindow().addFlags(mWindowFlags); 363 364 // Create a solid color image to use as the content of the presentation. 365 ImageView view = new ImageView(getContext()); 366 view.setImageDrawable(new ColorDrawable(mColor)); 367 view.setLayoutParams(new LayoutParams( 368 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 369 setContentView(view); 370 } 371 } 372 373 /** 374 * Watches for an image with a large amount of some particular solid color to be shown. 375 */ 376 private final class ImageListener 377 implements ImageReader.OnImageAvailableListener { 378 private int mColor = -1; 379 380 public int getColor() { 381 synchronized (this) { 382 return mColor; 383 } 384 } 385 386 public boolean waitForColor(int color, long timeoutMillis) { 387 long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis; 388 synchronized (this) { 389 while (mColor != color) { 390 long now = SystemClock.uptimeMillis(); 391 if (now >= timeoutTime) { 392 return false; 393 } 394 try { 395 wait(timeoutTime - now); 396 } catch (InterruptedException ex) { 397 } 398 } 399 return true; 400 } 401 } 402 403 @Override 404 public void onImageAvailable(ImageReader reader) { 405 mImageReaderLock.lock(); 406 try { 407 if (reader != mImageReader) { 408 return; 409 } 410 411 Log.d(TAG, "New image available from virtual display."); 412 413 // Get the latest buffer. 414 Image image = reader.acquireLatestImage(); 415 if (image != null) { 416 try { 417 // Scan for colors. 418 int color = scanImage(image); 419 synchronized (this) { 420 if (mColor != color) { 421 mColor = color; 422 notifyAll(); 423 } 424 } 425 } finally { 426 image.close(); 427 } 428 } 429 } finally { 430 mImageReaderLock.unlock(); 431 } 432 } 433 434 private int scanImage(Image image) { 435 final Image.Plane plane = image.getPlanes()[0]; 436 final ByteBuffer buffer = plane.getBuffer(); 437 final int width = image.getWidth(); 438 final int height = image.getHeight(); 439 final int pixelStride = plane.getPixelStride(); 440 final int rowStride = plane.getRowStride(); 441 final int rowPadding = rowStride - pixelStride * width; 442 443 Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height 444 + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride); 445 446 int offset = 0; 447 int blackPixels = 0; 448 int bluePixels = 0; 449 int greenPixels = 0; 450 int otherPixels = 0; 451 for (int y = 0; y < height; y++) { 452 for (int x = 0; x < width; x++) { 453 int pixel = 0; 454 pixel |= (buffer.get(offset) & 0xff) << 16; // R 455 pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G 456 pixel |= (buffer.get(offset + 2) & 0xff); // B 457 pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A 458 if (pixel == Color.BLACK || pixel == 0) { 459 blackPixels += 1; 460 } else if (pixel == BLUEISH) { 461 bluePixels += 1; 462 } else if (pixel == GREENISH) { 463 greenPixels += 1; 464 } else { 465 otherPixels += 1; 466 if (otherPixels < 10) { 467 Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel)); 468 } 469 } 470 offset += pixelStride; 471 } 472 offset += rowPadding; 473 } 474 475 // Return a color if it represents more than one quarter of the pixels. 476 // We use this threshold in case the display is being letterboxed when 477 // mirroring so there might be large black bars on the sides, which is normal. 478 Log.d(TAG, "- Pixels: " + blackPixels + " black, " 479 + bluePixels + " blue, " 480 + greenPixels + " green, " 481 + otherPixels + " other"); 482 final int threshold = width * height / 4; 483 if (bluePixels > threshold) { 484 Log.d(TAG, "- Reporting blue."); 485 return BLUEISH; 486 } 487 if (greenPixels > threshold) { 488 Log.d(TAG, "- Reporting green."); 489 return GREENISH; 490 } 491 if (blackPixels > threshold) { 492 Log.d(TAG, "- Reporting black."); 493 return Color.BLACK; 494 } 495 Log.d(TAG, "- Reporting other."); 496 return -1; 497 } 498 } 499 } 500 501