summaryrefslogtreecommitdiffstats
path: root/core/tests
diff options
context:
space:
mode:
Diffstat (limited to 'core/tests')
-rw-r--r--core/tests/coretests/AndroidManifest.xml6
-rw-r--r--core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java508
2 files changed, 513 insertions, 1 deletions
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index f8b26bc..a2cc40c 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -94,7 +94,7 @@
<uses-permission android:name="android.permission.MOVE_PACKAGE" />
<uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
- <!--os storage test permissions -->
+ <!-- os storage test permissions -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.ASEC_ACCESS" />
<uses-permission android:name="android.permission.ASEC_CREATE" />
@@ -103,6 +103,10 @@
<uses-permission android:name="android.permission.ASEC_RENAME" />
<uses-permission android:name="android.permission.SHUTDOWN" />
+ <!-- virtual display test permissions -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
+ <uses-permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" />
+
<!-- accessibility test permissions -->
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
new file mode 100644
index 0000000..ebecf2e
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -0,0 +1,508 @@
+/*
+ * 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.hardware.display;
+
+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.
+ *
+ * Contains additional tests that cannot be included in CTS because they require
+ * system permissions. See also the CTS version of VirtualDisplayTest.
+ */
+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);
+ }
+
+ /**
+ * Ensures that an application can create a public virtual display and show
+ * its own windows on it. This test requires the CAPTURE_VIDEO_OUTPUT permission.
+ *
+ * Because this test does not have an activity token, we use the TOAST window
+ * type to create the window. Another choice might be SYSTEM_ALERT_WINDOW but
+ * that requires a permission.
+ */
+ public void testPublicPresentationVirtualDisplay() throws Exception {
+ VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
+ WIDTH, HEIGHT, DENSITY, mSurface,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
+ assertNotNull("virtual display must not be null", virtualDisplay);
+
+ Display display = virtualDisplay.getDisplay();
+ try {
+ assertDisplayRegistered(display, Display.FLAG_PRESENTATION);
+
+ // Mirroring case.
+ // Show a window on the default display. It should be mirrored to the
+ // virtual display automatically.
+ Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ assertDisplayCanShowPresentation("mirrored window",
+ defaultDisplay, GREENISH,
+ WindowManager.LayoutParams.TYPE_TOAST, 0);
+
+ // Mirroring case with secure window (but display is not secure).
+ // Show a window on the default display. It should be replaced with black on
+ // the virtual display.
+ assertDisplayCanShowPresentation("mirrored secure window on non-secure display",
+ defaultDisplay, Color.BLACK,
+ WindowManager.LayoutParams.TYPE_TOAST,
+ WindowManager.LayoutParams.FLAG_SECURE);
+
+ // Presentation case.
+ // Show a normal presentation on the display.
+ assertDisplayCanShowPresentation("presentation window",
+ display, BLUEISH,
+ WindowManager.LayoutParams.TYPE_TOAST, 0);
+
+ // Presentation case with secure window (but display is not secure).
+ // Show a normal presentation on the display. It should be replaced with black.
+ assertDisplayCanShowPresentation("secure presentation window on non-secure display",
+ display, Color.BLACK,
+ WindowManager.LayoutParams.TYPE_TOAST,
+ WindowManager.LayoutParams.FLAG_SECURE);
+ } finally {
+ virtualDisplay.release();
+ }
+ assertDisplayUnregistered(display);
+ }
+
+ /**
+ * Ensures that an application can create a secure public virtual display and show
+ * its own windows on it. This test requires the CAPTURE_SECURE_VIDEO_OUTPUT permission.
+ *
+ * Because this test does not have an activity token, we use the TOAST window
+ * type to create the window. Another choice might be SYSTEM_ALERT_WINDOW but
+ * that requires a permission.
+ */
+ public void testSecurePublicPresentationVirtualDisplay() throws Exception {
+ VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
+ WIDTH, HEIGHT, DENSITY, mSurface,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
+ assertNotNull("virtual display must not be null", virtualDisplay);
+
+ Display display = virtualDisplay.getDisplay();
+ try {
+ assertDisplayRegistered(display, Display.FLAG_PRESENTATION | Display.FLAG_SECURE);
+
+ // Mirroring case with secure window (and display is secure).
+ // Show a window on the default display. It should be mirrored to the
+ // virtual display automatically.
+ Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ assertDisplayCanShowPresentation("mirrored secure window on secure display",
+ defaultDisplay, GREENISH,
+ WindowManager.LayoutParams.TYPE_TOAST,
+ WindowManager.LayoutParams.FLAG_SECURE);
+
+ // Presentation case with secure window (and display is secure).
+ // Show a normal presentation on the display.
+ assertDisplayCanShowPresentation("secure presentation window on secure display",
+ display, BLUEISH,
+ WindowManager.LayoutParams.TYPE_TOAST,
+ WindowManager.LayoutParams.FLAG_SECURE);
+ } 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;
+ }
+ }
+}
+