1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 import static android.view.WindowInsets.Type.displayCutout; 21 import static android.view.WindowInsets.Type.statusBars; 22 23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 24 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertTrue; 27 28 import android.app.Activity; 29 import android.app.Instrumentation; 30 import android.content.Context; 31 import android.graphics.Bitmap; 32 import android.graphics.Canvas; 33 import android.graphics.Color; 34 import android.graphics.GraphicBuffer; 35 import android.graphics.Insets; 36 import android.graphics.PixelFormat; 37 import android.graphics.Point; 38 import android.graphics.Rect; 39 import android.hardware.DataSpace; 40 import android.hardware.HardwareBuffer; 41 import android.os.Bundle; 42 import android.os.Handler; 43 import android.os.Looper; 44 import android.os.ServiceManager; 45 import android.platform.test.annotations.Presubmit; 46 import android.view.IWindowManager; 47 import android.view.PointerIcon; 48 import android.view.SurfaceControl; 49 import android.view.cts.surfacevalidator.BitmapPixelChecker; 50 import android.view.cts.surfacevalidator.SaveBitmapHelper; 51 import android.window.ScreenCapture; 52 import android.window.ScreenCapture.ScreenshotHardwareBuffer; 53 import android.window.ScreenCapture.SynchronousScreenCaptureListener; 54 55 import androidx.annotation.Nullable; 56 import androidx.test.filters.SmallTest; 57 import androidx.test.rule.ActivityTestRule; 58 59 import org.junit.Before; 60 import org.junit.Rule; 61 import org.junit.Test; 62 import org.junit.rules.TestName; 63 64 import java.util.concurrent.CountDownLatch; 65 import java.util.concurrent.TimeUnit; 66 67 /** 68 * Build/Install/Run: 69 * atest WmTests:ScreenshotTests 70 */ 71 @SmallTest 72 @Presubmit 73 public class ScreenshotTests { 74 private static final int BUFFER_WIDTH = 100; 75 private static final int BUFFER_HEIGHT = 100; 76 77 private final Instrumentation mInstrumentation = getInstrumentation(); 78 @Rule 79 public TestName mTestName = new TestName(); 80 81 @Rule 82 public ActivityTestRule<ScreenshotActivity> mActivityRule = 83 new ActivityTestRule<>(ScreenshotActivity.class); 84 85 private ScreenshotActivity mActivity; 86 87 @Before setup()88 public void setup() { 89 mActivity = mActivityRule.getActivity(); 90 mInstrumentation.waitForIdleSync(); 91 } 92 93 @Test testScreenshotSecureLayers()94 public void testScreenshotSecureLayers() { 95 SurfaceControl secureSC = new SurfaceControl.Builder() 96 .setName("SecureChildSurfaceControl") 97 .setBLASTLayer() 98 .setCallsite("makeSecureSurfaceControl") 99 .setSecure(true) 100 .build(); 101 102 SurfaceControl.Transaction t = mActivity.addChildSc(secureSC); 103 mInstrumentation.waitForIdleSync(); 104 105 GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT, 106 PixelFormat.RGBA_8888, 107 GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER 108 | GraphicBuffer.USAGE_SW_WRITE_RARELY); 109 110 Canvas canvas = buffer.lockCanvas(); 111 canvas.drawColor(Color.RED); 112 buffer.unlockCanvasAndPost(canvas); 113 114 t.show(secureSC) 115 .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer)) 116 .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB) 117 .apply(true); 118 119 ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC) 120 .setCaptureSecureLayers(true) 121 .setChildrenOnly(false) 122 .build(); 123 ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = ScreenCapture.captureLayers(args); 124 assertNotNull(hardwareBuffer); 125 126 Bitmap screenshot = hardwareBuffer.asBitmap(); 127 assertNotNull(screenshot); 128 129 Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); 130 screenshot.recycle(); 131 132 BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED); 133 Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight()); 134 int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); 135 int sizeOfBitmap = bounds.width() * bounds.height(); 136 boolean success = numMatchingPixels == sizeOfBitmap; 137 swBitmap.recycle(); 138 139 assertTrue(success); 140 } 141 142 @Test testCaptureDisplay()143 public void testCaptureDisplay() throws Exception { 144 IWindowManager windowManager = IWindowManager.Stub.asInterface( 145 ServiceManager.getService(Context.WINDOW_SERVICE)); 146 SurfaceControl sc = new SurfaceControl.Builder() 147 .setName("Layer") 148 .setCallsite("testCaptureDisplay") 149 .build(); 150 151 SurfaceControl.Transaction t = mActivity.addChildSc(sc); 152 mInstrumentation.waitForIdleSync(); 153 154 GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT, 155 PixelFormat.RGBA_8888, 156 GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER 157 | GraphicBuffer.USAGE_SW_WRITE_RARELY); 158 159 Canvas canvas = buffer.lockCanvas(); 160 canvas.drawColor(Color.RED); 161 buffer.unlockCanvasAndPost(canvas); 162 163 Point point = mActivity.getPositionBelowStatusBar(); 164 t.show(sc) 165 .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer)) 166 .setDataSpace(sc, DataSpace.DATASPACE_SRGB) 167 .setPosition(sc, point.x, point.y) 168 .apply(true); 169 170 SynchronousScreenCaptureListener syncScreenCapture = 171 ScreenCapture.createSyncCaptureListener(); 172 windowManager.captureDisplay(DEFAULT_DISPLAY, null, syncScreenCapture); 173 ScreenshotHardwareBuffer hardwareBuffer = syncScreenCapture.getBuffer(); 174 assertNotNull(hardwareBuffer); 175 176 Bitmap screenshot = hardwareBuffer.asBitmap(); 177 assertNotNull(screenshot); 178 179 Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); 180 screenshot.recycle(); 181 182 BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED); 183 Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y); 184 int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); 185 int pixelMatchSize = bounds.width() * bounds.height(); 186 boolean success = numMatchingPixels == pixelMatchSize; 187 188 if (!success) { 189 SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage"); 190 } 191 swBitmap.recycle(); 192 assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize, 193 success); 194 } 195 196 public static class ScreenshotActivity extends Activity { 197 private static final long WAIT_TIMEOUT_S = 5; 198 private final Handler mHandler = new Handler(Looper.getMainLooper()); 199 200 @Override onCreate(@ullable Bundle savedInstanceState)201 protected void onCreate(@Nullable Bundle savedInstanceState) { 202 super.onCreate(savedInstanceState); 203 getWindow().getDecorView().setPointerIcon( 204 PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); 205 } 206 addChildSc(SurfaceControl surfaceControl)207 SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) { 208 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 209 CountDownLatch countDownLatch = new CountDownLatch(1); 210 mHandler.post(() -> { 211 t.merge(getWindow().getRootSurfaceControl().buildReparentTransaction( 212 surfaceControl)); 213 countDownLatch.countDown(); 214 }); 215 216 try { 217 countDownLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 218 } catch (InterruptedException e) { 219 } 220 return t; 221 } 222 getPositionBelowStatusBar()223 public Point getPositionBelowStatusBar() { 224 Insets statusBarInsets = getWindow() 225 .getDecorView() 226 .getRootWindowInsets() 227 .getInsets(statusBars() | displayCutout()); 228 229 return new Point(statusBarInsets.left, statusBarInsets.top); 230 } 231 } 232 } 233