From f724c277d3362dbc8099fcbf8674609a424cd2ee Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Wed, 7 Aug 2013 14:17:04 -0700 Subject: [PATCH] Add more virtual display tests. We can't test everything in CTS because some features require system permissions. So this is another copy of the CTS test with more stuff that we can build with the system cert. Change-Id: Ied5a456a0810d38d307b6dfbad0f770cb480b4ee --- core/tests/coretests/AndroidManifest.xml | 6 +- .../hardware/display/VirtualDisplayTest.java | 508 +++++++++++++++++++++ media/jni/android_media_ImageReader.cpp | 5 +- 3 files changed, 516 insertions(+), 3 deletions(-) create mode 100644 core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f8b26bc58334..a2cc40cee252 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -94,7 +94,7 @@ - + @@ -103,6 +103,10 @@ + + + + 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 000000000000..ebecf2e38e6f --- /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; + } + } +} + diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index 0b429f65878c..cd589ded22c2 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -701,11 +701,12 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz, } status_t res = consumer->lockNextBuffer(buffer); if (res != NO_ERROR) { - ALOGE("%s Fail to lockNextBuffer with error: %d ", __FUNCTION__, res); + if (res != BAD_VALUE /*no buffers*/) { + ALOGE("%s Fail to lockNextBuffer with error: %d ", __FUNCTION__, res); + } return false; } - // Check if the left-top corner of the crop rect is origin, we currently assume this point is // zero, will revist this once this assumption turns out problematic. Point lt = buffer->crop.leftTop(); -- 2.11.0