diff options
Diffstat (limited to 'core/tests')
-rw-r--r-- | core/tests/coretests/AndroidManifest.xml | 6 | ||||
-rw-r--r-- | core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java | 508 |
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; + } + } +} + |