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
17package android.hardware.display;
18
19import android.app.Presentation;
20import android.content.Context;
21import android.graphics.Color;
22import android.graphics.PixelFormat;
23import android.graphics.Point;
24import android.graphics.drawable.ColorDrawable;
25import android.hardware.display.DisplayManager;
26import android.hardware.display.VirtualDisplay;
27import android.media.Image;
28import android.media.ImageReader;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Looper;
32import android.os.SystemClock;
33import android.test.AndroidTestCase;
34import android.util.DisplayMetrics;
35import android.util.Log;
36import android.view.Display;
37import android.view.Surface;
38import android.view.ViewGroup.LayoutParams;
39import android.view.WindowManager;
40import android.widget.ImageView;
41
42import java.nio.ByteBuffer;
43import java.util.concurrent.locks.Lock;
44import 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 */
52public 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