Home | History | Annotate | Download | only in display
      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