| /* |
| * 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.HandlerThread; |
| 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; |
| private HandlerThread mCheckThread; |
| private Handler mCheckHandler; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| |
| mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE); |
| mHandler = new Handler(Looper.getMainLooper()); |
| mImageListener = new ImageListener(); |
| // thread for image checking |
| mCheckThread = new HandlerThread("TestHandler"); |
| mCheckThread.start(); |
| mCheckHandler = new Handler(mCheckThread.getLooper()); |
| |
| mImageReaderLock.lock(); |
| try { |
| mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2); |
| mImageReader.setOnImageAvailableListener(mImageListener, mCheckHandler); |
| 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(); |
| } |
| mCheckThread.quit(); |
| } |
| |
| /** |
| * 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); |
| assertEquals(mSurface, virtualDisplay.getSurface()); |
| |
| // 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); |
| assertEquals(mSurface, virtualDisplay.getSurface()); |
| |
| // 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 virtual display and show |
| * its own windows on it where the surface is attached or detached dynamically. |
| */ |
| public void testPrivateVirtualDisplayWithDynamicSurface() throws Exception { |
| VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME, |
| WIDTH, HEIGHT, DENSITY, null, 0); |
| assertNotNull("virtual display must not be null", virtualDisplay); |
| |
| Display display = virtualDisplay.getDisplay(); |
| try { |
| assertDisplayRegistered(display, Display.FLAG_PRIVATE); |
| assertNull(virtualDisplay.getSurface()); |
| |
| // Attach the surface. |
| virtualDisplay.setSurface(mSurface); |
| assertEquals(mSurface, virtualDisplay.getSurface()); |
| |
| // Show a private presentation on the display. |
| assertDisplayCanShowPresentation("private presentation window", |
| display, BLUEISH, |
| WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0); |
| |
| // Detach the surface. |
| virtualDisplay.setSurface(null); |
| assertNull(virtualDisplay.getSurface()); |
| } 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."); |
| // Get the latest buffer |
| Image image = reader.acquireLatestImage(); |
| if (image != null) { |
| try { |
| // Scan for colors. |
| int color = scanImage(image); |
| synchronized (this) { |
| if (mColor != color) { |
| mColor = color; |
| notifyAll(); |
| } |
| } |
| } finally { |
| image.close(); |
| } |
| } |
| } 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; |
| } |
| } |
| } |
| |