Add CTS tests for virtual displays.

Change-Id: Ie2996b963e56307b9fa54ce1a1bd9838c8b88772
diff --git a/tests/tests/display/AndroidManifest.xml b/tests/tests/display/AndroidManifest.xml
index 1ddce5a..d1386d1 100644
--- a/tests/tests/display/AndroidManifest.xml
+++ b/tests/tests/display/AndroidManifest.xml
@@ -19,6 +19,7 @@
     package="com.android.cts.display">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 595f7148..4c7116d 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -174,6 +174,6 @@
     public void testFlags() {
         Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
 
-        assertEquals(0, display.getFlags());
+        assertEquals(Display.FLAG_PRESENTATION, display.getFlags());
     }
 }
diff --git a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
new file mode 100644
index 0000000..54f076f
--- /dev/null
+++ b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.display.cts;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Tests that applications can create virtual displays and present content on them.
+ *
+ * This CTS test is unable to test public virtual displays since special permissions
+ * are required.  See also framework VirtualDisplayTest unit tests.
+ */
+public class VirtualDisplayTest extends AndroidTestCase {
+    private static final String TAG = "VirtualDisplayTest";
+
+    private static final String NAME = TAG;
+    private static final int WIDTH = 720;
+    private static final int HEIGHT = 480;
+    private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
+    private static final int TIMEOUT = 10000;
+
+    // Colors that we use as a signal to determine whether some desired content was
+    // drawn.  The colors themselves doesn't matter but we choose them to have with distinct
+    // values for each color channel so as to detect possible RGBA vs. BGRA buffer format issues.
+    // We should only observe RGBA buffers but some graphics drivers might incorrectly
+    // deliver BGRA buffers to virtual displays instead.
+    private static final int BLUEISH = 0xff1122ee;
+    private static final int GREENISH = 0xff33dd44;
+
+    private DisplayManager mDisplayManager;
+    private Handler mHandler;
+    private final Lock mImageReaderLock = new ReentrantLock(true /*fair*/);
+    private ImageReader mImageReader;
+    private Surface mSurface;
+    private ImageListener mImageListener;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+        mHandler = new Handler(Looper.getMainLooper());
+        mImageListener = new ImageListener();
+
+        mImageReaderLock.lock();
+        try {
+            mImageReader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
+            mImageReader.setImageAvailableListener(mImageListener, mHandler);
+            mSurface = mImageReader.getSurface();
+        } finally {
+            mImageReaderLock.unlock();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        mImageReaderLock.lock();
+        try {
+            mImageReader.close();
+            mImageReader = null;
+            mSurface = null;
+        } finally {
+            mImageReaderLock.unlock();
+        }
+    }
+
+    /**
+     * Ensures that an application can create a private virtual display and show
+     * its own windows on it.
+     */
+    public void testPrivateVirtualDisplay() throws Exception {
+        VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
+                WIDTH, HEIGHT, DENSITY, mSurface, 0);
+        assertNotNull("virtual display must not be null", virtualDisplay);
+
+        Display display = virtualDisplay.getDisplay();
+        try {
+            assertDisplayRegistered(display, Display.FLAG_PRIVATE);
+
+            // Show a private presentation on the display.
+            assertDisplayCanShowPresentation("private presentation window",
+                    display, BLUEISH,
+                    WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0);
+        } finally {
+            virtualDisplay.release();
+        }
+        assertDisplayUnregistered(display);
+    }
+
+    /**
+     * Ensures that an application can create a private presentation virtual display and show
+     * its own windows on it.
+     */
+    public void testPrivatePresentationVirtualDisplay() throws Exception {
+        VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
+                WIDTH, HEIGHT, DENSITY, mSurface,
+                DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
+        assertNotNull("virtual display must not be null", virtualDisplay);
+
+        Display display = virtualDisplay.getDisplay();
+        try {
+            assertDisplayRegistered(display, Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION);
+
+            // Show a private presentation on the display.
+            assertDisplayCanShowPresentation("private presentation window",
+                    display, BLUEISH,
+                    WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0);
+        } finally {
+            virtualDisplay.release();
+        }
+        assertDisplayUnregistered(display);
+    }
+
+    private void assertDisplayRegistered(Display display, int flags) {
+        assertNotNull("display object must not be null", display);
+        assertTrue("display must be valid", display.isValid());
+        assertTrue("display id must be unique",
+                display.getDisplayId() != Display.DEFAULT_DISPLAY);
+        assertEquals("display must have correct flags", flags, display.getFlags());
+        assertEquals("display name must match supplied name", NAME, display.getName());
+        Point size = new Point();
+        display.getSize(size);
+        assertEquals("display width must match supplied width", WIDTH, size.x);
+        assertEquals("display height must match supplied height", HEIGHT, size.y);
+        assertEquals("display rotation must be 0",
+                Surface.ROTATION_0, display.getRotation());
+        assertNotNull("display must be registered",
+                findDisplay(mDisplayManager.getDisplays(), NAME));
+
+        if ((flags & Display.FLAG_PRESENTATION) != 0) {
+            assertNotNull("display must be registered as a presentation display",
+                    findDisplay(mDisplayManager.getDisplays(
+                            DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME));
+        } else {
+            assertNull("display must not be registered as a presentation display",
+                    findDisplay(mDisplayManager.getDisplays(
+                            DisplayManager.DISPLAY_CATEGORY_PRESENTATION), NAME));
+        }
+    }
+
+    private void assertDisplayUnregistered(Display display) {
+        assertNull("display must no longer be registered after being removed",
+                findDisplay(mDisplayManager.getDisplays(), NAME));
+        assertFalse("display must no longer be valid", display.isValid());
+    }
+
+    private void assertDisplayCanShowPresentation(String message, final Display display,
+            final int color, final int windowType, final int windowFlags) {
+        // At this point, we should not have seen any blue.
+        assertTrue(message + ": display should not show content before window is shown",
+                mImageListener.getColor() != color);
+
+        final TestPresentation[] presentation = new TestPresentation[1];
+        try {
+            // Show the presentation.
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    presentation[0] = new TestPresentation(getContext(), display,
+                            color, windowType, windowFlags);
+                    presentation[0].show();
+                }
+            });
+
+            // Wait for the blue to be seen.
+            assertTrue(message + ": display should show content after window is shown",
+                    mImageListener.waitForColor(color, TIMEOUT));
+        } finally {
+            if (presentation[0] != null) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        presentation[0].dismiss();
+                    }
+                });
+            }
+        }
+    }
+
+    private void runOnUiThread(Runnable runnable) {
+        Runnable waiter = new Runnable() {
+            @Override
+            public void run() {
+                synchronized (this) {
+                    notifyAll();
+                }
+            }
+        };
+        synchronized (waiter) {
+            mHandler.post(runnable);
+            mHandler.post(waiter);
+            try {
+                waiter.wait(TIMEOUT);
+            } catch (InterruptedException ex) {
+            }
+        }
+    }
+
+    private Display findDisplay(Display[] displays, String name) {
+        for (int i = 0; i < displays.length; i++) {
+            if (displays[i].getName().equals(name)) {
+                return displays[i];
+            }
+        }
+        return null;
+    }
+
+    private final class TestPresentation extends Presentation {
+        private final int mColor;
+        private final int mWindowType;
+        private final int mWindowFlags;
+
+        public TestPresentation(Context context, Display display,
+                int color, int windowType, int windowFlags) {
+            super(context, display);
+            mColor = color;
+            mWindowType = windowType;
+            mWindowFlags = windowFlags;
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            setTitle(TAG);
+            getWindow().setType(mWindowType);
+            getWindow().addFlags(mWindowFlags);
+
+            // Create a solid color image to use as the content of the presentation.
+            ImageView view = new ImageView(getContext());
+            view.setImageDrawable(new ColorDrawable(mColor));
+            view.setLayoutParams(new LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            setContentView(view);
+        }
+    }
+
+    /**
+     * Watches for an image with a large amount of some particular solid color to be shown.
+     */
+    private final class ImageListener
+            implements ImageReader.OnImageAvailableListener {
+        private int mColor = -1;
+
+        public int getColor() {
+            synchronized (this) {
+                return mColor;
+            }
+        }
+
+        public boolean waitForColor(int color, long timeoutMillis) {
+            long timeoutTime = SystemClock.uptimeMillis() + timeoutMillis;
+            synchronized (this) {
+                while (mColor != color) {
+                    long now = SystemClock.uptimeMillis();
+                    if (now >= timeoutTime) {
+                        return false;
+                    }
+                    try {
+                        wait(timeoutTime - now);
+                    } catch (InterruptedException ex) {
+                    }
+                }
+                return true;
+            }
+        }
+
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            mImageReaderLock.lock();
+            try {
+                if (reader != mImageReader) {
+                    return;
+                }
+
+                Log.d(TAG, "New image available from virtual display.");
+                Image image = reader.getNextImage();
+                if (image != null) {
+                    try {
+                        // Get the latest buffer.
+                        for (;;) {
+                            Image nextImage = reader.getNextImage();
+                            if (nextImage == null) {
+                                break;
+                            }
+                            reader.releaseImage(image);
+                            image = nextImage;
+                        }
+
+                        // Scan for colors.
+                        int color = scanImage(image);
+                        synchronized (this) {
+                            if (mColor != color) {
+                                mColor = color;
+                                notifyAll();
+                            }
+                        }
+                    } finally {
+                        reader.releaseImage(image);
+                    }
+                }
+            } finally {
+                mImageReaderLock.unlock();
+            }
+        }
+
+        private int scanImage(Image image) {
+            final Image.Plane plane = image.getPlanes()[0];
+            final ByteBuffer buffer = plane.getBuffer();
+            final int width = image.getWidth();
+            final int height = image.getHeight();
+            final int pixelStride = plane.getPixelStride();
+            final int rowStride = plane.getRowStride();
+            final int rowPadding = rowStride - pixelStride * width;
+
+            Log.d(TAG, "- Scanning image: width=" + width + ", height=" + height
+                    + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride);
+
+            int offset = 0;
+            int blackPixels = 0;
+            int bluePixels = 0;
+            int greenPixels = 0;
+            int otherPixels = 0;
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+                    int pixel = 0;
+                    pixel |= (buffer.get(offset) & 0xff) << 16;     // R
+                    pixel |= (buffer.get(offset + 1) & 0xff) << 8;  // G
+                    pixel |= (buffer.get(offset + 2) & 0xff);       // B
+                    pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
+                    if (pixel == Color.BLACK || pixel == 0) {
+                        blackPixels += 1;
+                    } else if (pixel == BLUEISH) {
+                        bluePixels += 1;
+                    } else if (pixel == GREENISH) {
+                        greenPixels += 1;
+                    } else {
+                        otherPixels += 1;
+                        if (otherPixels < 10) {
+                            Log.d(TAG, "- Found unexpected color: " + Integer.toHexString(pixel));
+                        }
+                    }
+                    offset += pixelStride;
+                }
+                offset += rowPadding;
+            }
+
+            // Return a color if it represents more than one quarter of the pixels.
+            // We use this threshold in case the display is being letterboxed when
+            // mirroring so there might be large black bars on the sides, which is normal.
+            Log.d(TAG, "- Pixels: " + blackPixels + " black, "
+                    + bluePixels + " blue, "
+                    + greenPixels + " green, "
+                    + otherPixels + " other");
+            final int threshold = width * height / 4;
+            if (bluePixels > threshold) {
+                Log.d(TAG, "- Reporting blue.");
+                return BLUEISH;
+            }
+            if (greenPixels > threshold) {
+                Log.d(TAG, "- Reporting green.");
+                return GREENISH;
+            }
+            if (blackPixels > threshold) {
+                Log.d(TAG, "- Reporting black.");
+                return Color.BLACK;
+            }
+            Log.d(TAG, "- Reporting other.");
+            return -1;
+        }
+    }
+}
+
diff --git a/tests/tests/permission/src/android/permission/cts/NoCapturePermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoCapturePermissionTest.java
new file mode 100644
index 0000000..5fa52d2
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NoCapturePermissionTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.DisplayMetrics;
+
+/**
+ * Verify the capture system audio output and video output permission requirements.
+ */
+public class NoCapturePermissionTest extends AndroidTestCase {
+    private static final String NAME = "VirtualDisplayTest";
+    private static final int WIDTH = 720;
+    private static final int HEIGHT = 480;
+    private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
+
+    /**
+     * Verify that DisplayManager.createVirtualDisplay() requires permissions to
+     * create public displays.
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT} or
+     *   {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT}.
+     */
+    @SmallTest
+    public void testCreatePublicVirtualDisplay() {
+        DisplayManager displayManager =
+                (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+        ImageReader reader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBX_8888, 1);
+        try {
+            displayManager.createVirtualDisplay(NAME, WIDTH, HEIGHT, DENSITY,
+                    reader.getSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC);
+            fail("DisplayManager.createVirtualDisplay() didn't throw SecurityException "
+                    + "as expected when creating public virtual display.");
+        } catch (SecurityException e) {
+            // expected
+        } finally {
+            reader.close();
+        }
+    }
+
+    /**
+     * Verify that DisplayManager.createVirtualDisplay() requires permissions to
+     * create secure displays.
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT}.
+     */
+    @SmallTest
+    public void testCreateSecureVirtualDisplay() {
+        DisplayManager displayManager =
+                (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+        ImageReader reader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBX_8888, 1);
+        try {
+            displayManager.createVirtualDisplay(NAME, WIDTH, HEIGHT, DENSITY,
+                    reader.getSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE);
+            fail("DisplayManager.createVirtualDisplay() didn't throw SecurityException "
+                    + "as expected when creating secure virtual display.");
+        } catch (SecurityException e) {
+            // expected
+        } finally {
+            reader.close();
+        }
+    }
+
+    /**
+     * Verify that DisplayManager.createVirtualDisplay() does not requires permissions to
+     * create private displays.
+     */
+    @SmallTest
+    public void testCreatePrivateVirtualDisplay() {
+        DisplayManager displayManager =
+                (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+        ImageReader reader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBX_8888, 1);
+        try {
+            VirtualDisplay display = displayManager.createVirtualDisplay(
+                    NAME, WIDTH, HEIGHT, DENSITY,
+                    reader.getSurface(), 0);
+            display.release();
+        } catch (SecurityException e) {
+            fail("DisplayManager.createVirtualDisplay() should not throw SecurityException "
+                    + "when creating private virtual display.");
+        } finally {
+            reader.close();
+        }
+    }
+}