blob: 0b58428e9bfbf7a75b3398010862fc2622a605e8 [file] [log] [blame]
/*
* 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 android.window.ScreenCapture;
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);
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
.setCaptureSecureLayers(true)
.setChildrenOnly(false)
.build();
ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = ScreenCapture.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);
}
}
}