Pixel copy tests for wide color gamut windows

This tests two cases:
- A WCG window with a readback in ARGB8888, values are clamped
  to [0..1]
- A WCG window with a readback in RGBA_F16, values are not
  clamped and remain in scRGB-nl color space (they go outside
  the [0..1] range)

Bug: 62728191
Test: CtsViewTestCases
Change-Id: I3b29607069eba945a828ae9edc17e8b576b21fbb
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 5d23183..7af3b42 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -175,6 +175,7 @@
                   android:screenOrientation="locked"
                   android:label="PixelCopyGLProducerCtsActivity"/>
 
+
         <activity android:name="android.view.cts.PixelCopyViewProducerActivity"
                   android:label="PixelCopyViewProducerActivity"
                   android:screenOrientation="portrait"
@@ -182,6 +183,14 @@
                   android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
                   android:configChanges="orientation|screenSize" />
 
+        <activity android:name="android.view.cts.PixelCopyWideGamutViewProducerActivity"
+                  android:label="PixelCopyWideGamutViewProducerActivity"
+                  android:screenOrientation="portrait"
+                  android:rotationAnimation="jumpcut"
+                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+                  android:configChanges="orientation|screenSize"
+                  android:colorMode="wideColorGamut" />
+
         <activity android:name="android.view.cts.FocusFinderCtsActivity"
                   android:screenOrientation="locked"
                   android:label="FocusFinderCtsActivity">
diff --git a/tests/tests/view/assets/prophoto.png b/tests/tests/view/assets/prophoto.png
new file mode 100644
index 0000000..8badba7
--- /dev/null
+++ b/tests/tests/view/assets/prophoto.png
Binary files differ
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTest.java b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
index 6310ce8..d94fe6f 100644
--- a/tests/tests/view/src/android/view/cts/PixelCopyTest.java
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
@@ -16,14 +16,8 @@
 
 package android.view.cts;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 import android.app.Instrumentation;
+import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Color;
@@ -36,6 +30,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Half;
 import android.util.Log;
 import android.view.PixelCopy;
 import android.view.Surface;
@@ -52,9 +47,18 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class PixelCopyTest {
@@ -73,6 +77,11 @@
             new ActivityTestRule<>(PixelCopyViewProducerActivity.class, false, false);
 
     @Rule
+    public ActivityTestRule<PixelCopyWideGamutViewProducerActivity>
+            mWideGamutWindowSourceActivityRule = new ActivityTestRule<>(
+                    PixelCopyWideGamutViewProducerActivity.class, false, false);
+
+    @Rule
     public SurfaceTextureRule mSurfaceRule = new SurfaceTextureRule();
 
     private Instrumentation mInstrumentation;
@@ -319,6 +328,125 @@
         } while (activity.rotate());
     }
 
+    @Test
+    public void testWindowProducerCopyToRGBA16F() {
+        Window window = waitForWindowProducerActivity();
+        PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
+
+        Bitmap bitmap;
+        do {
+            Rect src = makeWindowRect(0, 0, 100, 100);
+            bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16);
+            int result = mCopyHelper.request(window, src, bitmap);
+            // On OpenGL ES 2.0 devices a copy to RGBA_F16 can fail because there's
+            // not support for float textures
+            if (result != PixelCopy.ERROR_DESTINATION_INVALID) {
+                assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
+                assertEquals(Config.RGBA_F16, bitmap.getConfig());
+                assertBitmapQuadColor(bitmap,
+                        Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+                assertBitmapEdgeColor(bitmap, Color.YELLOW);
+            }
+        } while (activity.rotate());
+    }
+
+    private Window waitForWideGamutWindowProducerActivity() {
+        PixelCopyWideGamutViewProducerActivity activity =
+                mWideGamutWindowSourceActivityRule.launchActivity(null);
+        activity.waitForFirstDrawCompleted(3, TimeUnit.SECONDS);
+        return activity.getWindow();
+    }
+
+    private Rect makeWideGamutWindowRect(int left, int top, int right, int bottom) {
+        Rect r = new Rect(left, top, right, bottom);
+        mWideGamutWindowSourceActivityRule.getActivity().offsetForContent(r);
+        return r;
+    }
+
+    @Test
+    public void testWideGamutWindowProducerCopyToRGBA8888() {
+        Window window = waitForWideGamutWindowProducerActivity();
+        assertEquals(
+                ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT, window.getAttributes().getColorMode());
+
+        // Early out if the device does not support wide color gamut rendering
+        if (!window.getDecorView().getDisplay().isWideColorGamut()) {
+            return;
+        }
+
+        PixelCopyWideGamutViewProducerActivity activity =
+                mWideGamutWindowSourceActivityRule.getActivity();
+
+        Bitmap bitmap;
+        do {
+            Rect src = makeWideGamutWindowRect(0, 0, 128, 128);
+            bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
+            int result = mCopyHelper.request(window, src, bitmap);
+
+            assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
+            assertEquals(Config.ARGB_8888, bitmap.getConfig());
+
+            assertEquals("Top left", Color.RED, bitmap.getPixel(32, 32));
+            assertEquals("Top right", Color.GREEN, bitmap.getPixel(96, 32));
+            assertEquals("Bottom left", Color.BLUE, bitmap.getPixel(32, 96));
+            assertEquals("Bottom right", Color.YELLOW, bitmap.getPixel(96, 96));
+        } while (activity.rotate());
+    }
+
+    @Test
+    public void testWideGamutWindowProducerCopyToRGBA16F() {
+        Window window = waitForWideGamutWindowProducerActivity();
+        assertEquals(
+                ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT, window.getAttributes().getColorMode());
+
+        // Early out if the device does not support wide color gamut rendering
+        if (!window.getDecorView().getDisplay().isWideColorGamut()) {
+            return;
+        }
+
+        PixelCopyWideGamutViewProducerActivity activity =
+                mWideGamutWindowSourceActivityRule.getActivity();
+
+        Bitmap bitmap;
+        int i = 0;
+        do {
+            Rect src = makeWideGamutWindowRect(0, 0, 128, 128);
+            bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16);
+            int result = mCopyHelper.request(window, src, bitmap);
+
+            assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
+            assertEquals(Config.RGBA_F16, bitmap.getConfig());
+
+            ByteBuffer dst = ByteBuffer.allocateDirect(bitmap.getAllocationByteCount());
+            bitmap.copyPixelsToBuffer(dst);
+            dst.rewind();
+            dst.order(ByteOrder.LITTLE_ENDIAN);
+
+            // ProPhoto RGB red in scRGB-nl
+            assertEqualsRgba16f("Top left",     bitmap, 32, 32, dst,  1.36f, -0.52f, -0.09f, 1.0f);
+            // ProPhoto RGB green in scRGB-nl
+            assertEqualsRgba16f("Top right",    bitmap, 96, 32, dst, -0.87f,  1.10f, -0.43f, 1.0f);
+            // ProPhoto RGB blue in scRGB-nl
+            assertEqualsRgba16f("Bottom left",  bitmap, 32, 96, dst, -0.59f, -0.04f,  1.07f, 1.0f);
+            // ProPhoto RGB yellow in scRGB-nl
+            assertEqualsRgba16f("Bottom right", bitmap, 96, 96, dst,  1.12f,  1.00f, -0.44f, 1.0f);
+        } while (activity.rotate());
+    }
+
+    private static void assertEqualsRgba16f(String message, Bitmap bitmap, int x, int y,
+            ByteBuffer dst, float r, float g, float b, float a) {
+        int index = y * bitmap.getRowBytes() + (x << 3);
+        short cR = dst.getShort(index);
+        short cG = dst.getShort(index + 2);
+        short cB = dst.getShort(index + 4);
+        short cA = dst.getShort(index + 6);
+
+        assertEquals(message, r, Half.toFloat(cR), 0.01);
+        assertEquals(message, g, Half.toFloat(cG), 0.01);
+        assertEquals(message, b, Half.toFloat(cB), 0.01);
+        assertEquals(message, a, Half.toFloat(cA), 0.01);
+    }
+
     private void runGcAndFinalizersSync() {
         final CountDownLatch fence = new CountDownLatch(1);
         new Object() {
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyWideGamutViewProducerActivity.java b/tests/tests/view/src/android/view/cts/PixelCopyWideGamutViewProducerActivity.java
new file mode 100644
index 0000000..f697095
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/PixelCopyWideGamutViewProducerActivity.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 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.view.cts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.fail;
+
+public class PixelCopyWideGamutViewProducerActivity extends Activity implements OnDrawListener {
+    private static final int[] ORIENTATIONS = {
+            ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
+            ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
+            ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
+            ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
+    };
+    private int mCurrentOrientation = 0;
+    private View mContent;
+    private Rect mContentBounds = new Rect();
+    private CountDownLatch mFence = new CountDownLatch(3);
+    private boolean mSupportsRotation;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Check if the device supports both of portrait and landscape orientation screens.
+        final PackageManager pm = getPackageManager();
+        mSupportsRotation = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)
+                    && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
+        if (mSupportsRotation) {
+            setRequestedOrientation(ORIENTATIONS[mCurrentOrientation]);
+        }
+
+        mContent = new WideGamutBitmapView(this);
+        setContentView(mContent);
+        mContent.getViewTreeObserver().addOnDrawListener(this);
+    }
+
+    @Override
+    public void onDraw() {
+        final int requestedOrientation = ORIENTATIONS[mCurrentOrientation];
+        boolean screenPortrait =
+                requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+                || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+        boolean contentPortrait = mContent.getHeight() > mContent.getWidth();
+        if (mSupportsRotation && (screenPortrait != contentPortrait)) {
+            return;
+        }
+        mContent.post(() -> {
+            Point offset = new Point();
+            // We pass mContentBounds here just as a throwaway rect, we don't care about
+            // the visible rect just the global offset.
+            mContent.getGlobalVisibleRect(mContentBounds, offset);
+            mContentBounds.set(offset.x, offset.y,
+                    offset.x + mContent.getWidth(), offset.y + mContent.getHeight());
+            mFence.countDown();
+            if (mFence.getCount() > 0) {
+                mContent.invalidate();
+            }
+        });
+    }
+
+    public void waitForFirstDrawCompleted(int timeout, TimeUnit unit) {
+        try {
+            if (!mFence.await(timeout, unit)) {
+                fail("Timeout");
+            }
+        } catch (InterruptedException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public boolean rotate() {
+        if (!mSupportsRotation) {
+            // Do not rotate the screen if it is not supported.
+            return false;
+        }
+        mFence = new CountDownLatch(3);
+        runOnUiThread(() -> {
+            mCurrentOrientation = (mCurrentOrientation + 1) % ORIENTATIONS.length;
+            setRequestedOrientation(ORIENTATIONS[mCurrentOrientation]);
+        });
+        waitForFirstDrawCompleted(3, TimeUnit.SECONDS);
+        return mCurrentOrientation != 0;
+    }
+
+    void offsetForContent(Rect inOut) {
+        inOut.offset(mContentBounds.left, mContentBounds.top);
+    }
+
+    private static final class WideGamutBitmapView extends View {
+        private final Bitmap mBitmap;
+
+        WideGamutBitmapView(Context context) {
+            super(context);
+            // We use an asset to ensure aapt will not mess with the data
+            AssetManager assets = context.getResources().getAssets();
+            try (InputStream in = assets.open("prophoto.png")) {
+                mBitmap = BitmapFactory.decodeStream(in);
+            } catch (IOException e) {
+                throw new RuntimeException("Test failed: ", e);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null);
+        }
+    }
+}