Add CAPTURE_BLACKOUT_CONTENT permission

Added a permssion to allow an application to capture screenshots of
layers that would normally be blacked out.

Test: Builds, screenshots still work as before
Test: ScreenshotTests
Bug: 173746627
Change-Id: I27237c8e340904cbaa82c7e5793de35cab85d8de
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ffb8d1d..78184da 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5768,6 +5768,16 @@
                 android:protectionLevel="normal" />
     <uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"/>
 
+    <!-- Allows an application to take screenshots of layers that normally would be blacked out when
+         a screenshot is taken. Specifically, layers that have the flag
+         {@link android.view.SurfaceControl#SECURE} will be screenshot if the caller requests to
+         capture secure layers. Normally those layers will be rendered black.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"
+        android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 6bd0e0a..10df726 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -18,6 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.wm.shell">
     <!-- System permission required by WM Shell Task Organizer. -->
+    <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index de4698d..985b2d5 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -41,6 +41,7 @@
     <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"/>
 
     <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
     <application android:debuggable="true"
@@ -75,6 +76,7 @@
         <activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
                   android:turnScreenOn="true"
                   android:showWhenLocked="true" />
+        <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" />
     </application>
 
     <instrumentation
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
new file mode 100644
index 0000000..f542e29
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2021 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 com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.view.PointerIcon;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ *  atest WmTests:ScreenshotTests
+ */
+@SmallTest
+@Presubmit
+public class ScreenshotTests {
+    private static final int BUFFER_WIDTH = 100;
+    private static final int BUFFER_HEIGHT = 100;
+
+    private final Instrumentation mInstrumentation = getInstrumentation();
+
+    @Rule
+    public ActivityTestRule<ScreenshotActivity> mActivityRule =
+            new ActivityTestRule<>(ScreenshotActivity.class);
+
+    private ScreenshotActivity mActivity;
+
+    @Before
+    public void setup() {
+        mActivity = mActivityRule.getActivity();
+        mInstrumentation.waitForIdleSync();
+    }
+
+    @Test
+    public void testScreenshotSecureLayers() {
+        SurfaceControl secureSC = new SurfaceControl.Builder()
+                .setName("SecureChildSurfaceControl")
+                .setBLASTLayer()
+                .setCallsite("makeSecureSurfaceControl")
+                .setSecure(true)
+                .build();
+
+        SurfaceControl.Transaction t = mActivity.addChildSc(secureSC);
+        mInstrumentation.waitForIdleSync();
+
+        GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT,
+                PixelFormat.RGBA_8888,
+                GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+                        | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+
+        Canvas canvas = buffer.lockCanvas();
+        canvas.drawColor(Color.RED);
+        buffer.unlockCanvasAndPost(canvas);
+
+        t.show(secureSC)
+                .setBuffer(secureSC, buffer)
+                .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB))
+                .apply(true);
+
+        SurfaceControl.LayerCaptureArgs args = new SurfaceControl.LayerCaptureArgs.Builder(secureSC)
+                .setCaptureSecureLayers(true)
+                .setChildrenOnly(false)
+                .build();
+        SurfaceControl.ScreenshotHardwareBuffer hardwareBuffer = SurfaceControl.captureLayers(args);
+        assertNotNull(hardwareBuffer);
+
+        Bitmap screenshot = hardwareBuffer.asBitmap();
+        assertNotNull(screenshot);
+
+        Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+        screenshot.recycle();
+
+        int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap,
+                new PixelColor(PixelColor.RED));
+        long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight();
+        boolean success = numMatchingPixels == sizeOfBitmap;
+        swBitmap.recycle();
+
+        assertTrue(success);
+    }
+
+    public static class ScreenshotActivity extends Activity {
+        private static final long WAIT_TIMEOUT_S = 5;
+        private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            getWindow().getDecorView().setPointerIcon(
+                    PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+
+        SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            CountDownLatch countDownLatch = new CountDownLatch(1);
+            mHandler.post(() -> {
+                t.merge(getWindow().getRootSurfaceControl().buildReparentTransaction(
+                        surfaceControl));
+                countDownLatch.countDown();
+            });
+
+            try {
+                countDownLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+            }
+            return t;
+        }
+    }
+
+    public abstract static class PixelChecker {
+        static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) {
+            int numMatchingPixels = 0;
+            for (int x = 0; x < bitmap.getWidth(); x++) {
+                for (int y = 0; y < bitmap.getHeight(); y++) {
+                    int color = bitmap.getPixel(x, y);
+                    if (matchesColor(pixelColor, color)) {
+                        numMatchingPixels++;
+                    }
+                }
+            }
+            return numMatchingPixels;
+        }
+
+        static boolean matchesColor(PixelColor expectedColor, int color) {
+            final float red = Color.red(color);
+            final float green = Color.green(color);
+            final float blue = Color.blue(color);
+            final float alpha = Color.alpha(color);
+
+            return alpha <= expectedColor.mMaxAlpha
+                    && alpha >= expectedColor.mMinAlpha
+                    && red <= expectedColor.mMaxRed
+                    && red >= expectedColor.mMinRed
+                    && green <= expectedColor.mMaxGreen
+                    && green >= expectedColor.mMinGreen
+                    && blue <= expectedColor.mMaxBlue
+                    && blue >= expectedColor.mMinBlue;
+        }
+    }
+
+    public static class PixelColor {
+        public static final int BLACK = 0xFF000000;
+        public static final int RED = 0xFF0000FF;
+        public static final int GREEN = 0xFF00FF00;
+        public static final int BLUE = 0xFFFF0000;
+        public static final int YELLOW = 0xFF00FFFF;
+        public static final int MAGENTA = 0xFFFF00FF;
+        public static final int WHITE = 0xFFFFFFFF;
+
+        public static final int TRANSPARENT_RED = 0x7F0000FF;
+        public static final int TRANSPARENT_BLUE = 0x7FFF0000;
+        public static final int TRANSPARENT = 0x00000000;
+
+        // Default to black
+        public short mMinAlpha;
+        public short mMaxAlpha;
+        public short mMinRed;
+        public short mMaxRed;
+        public short mMinBlue;
+        public short mMaxBlue;
+        public short mMinGreen;
+        public short mMaxGreen;
+
+        public PixelColor(int color) {
+            short alpha = (short) ((color >> 24) & 0xFF);
+            short blue = (short) ((color >> 16) & 0xFF);
+            short green = (short) ((color >> 8) & 0xFF);
+            short red = (short) (color & 0xFF);
+
+            mMinAlpha = (short) getMinValue(alpha);
+            mMaxAlpha = (short) getMaxValue(alpha);
+            mMinRed = (short) getMinValue(red);
+            mMaxRed = (short) getMaxValue(red);
+            mMinBlue = (short) getMinValue(blue);
+            mMaxBlue = (short) getMaxValue(blue);
+            mMinGreen = (short) getMinValue(green);
+            mMaxGreen = (short) getMaxValue(green);
+        }
+
+        public PixelColor() {
+            this(BLACK);
+        }
+
+        private int getMinValue(short color) {
+            return Math.max(color - 4, 0);
+        }
+
+        private int getMaxValue(short color) {
+            return Math.min(color + 4, 0xFF);
+        }
+    }
+}