Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b0e2010 authored by Chavi Weingarten's avatar Chavi Weingarten Committed by Android (Google) Code Review
Browse files

Merge "Add CAPTURE_BLACKOUT_CONTENT permission" into sc-dev

parents 8de89d0f fb796315
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -5772,6 +5772,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. -->
+1 −0
Original line number Diff line number Diff line
@@ -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" />
+2 −0
Original line number Diff line number Diff line
@@ -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
+234 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}