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.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry; 20 import static android.view.Display.DEFAULT_DISPLAY; 21 import static android.view.WindowInsets.Type.displayCutout; 22 import static android.view.WindowInsets.Type.statusBars; 23 24 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.assertTrue; 29 30 import android.app.Activity; 31 import android.app.Instrumentation; 32 import android.content.Context; 33 import android.graphics.Bitmap; 34 import android.graphics.Canvas; 35 import android.graphics.Color; 36 import android.graphics.GraphicBuffer; 37 import android.graphics.Insets; 38 import android.graphics.PixelFormat; 39 import android.graphics.Point; 40 import android.graphics.Rect; 41 import android.hardware.DataSpace; 42 import android.hardware.HardwareBuffer; 43 import android.os.Bundle; 44 import android.os.Handler; 45 import android.os.Looper; 46 import android.os.ServiceManager; 47 import android.platform.test.annotations.Presubmit; 48 import android.server.wm.BuildUtils; 49 import android.view.IWindowManager; 50 import android.view.PointerIcon; 51 import android.view.SurfaceControl; 52 import android.view.cts.surfacevalidator.BitmapPixelChecker; 53 import android.view.cts.surfacevalidator.SaveBitmapHelper; 54 import android.window.ScreenCapture; 55 import android.window.ScreenCapture.ScreenshotHardwareBuffer; 56 57 import androidx.annotation.Nullable; 58 import androidx.test.filters.SmallTest; 59 import androidx.test.rule.ActivityTestRule; 60 61 import com.android.server.wm.utils.CommonUtils; 62 63 import org.junit.After; 64 import org.junit.Before; 65 import org.junit.Rule; 66 import org.junit.Test; 67 import org.junit.rules.TestName; 68 69 import java.time.Duration; 70 import java.util.concurrent.CountDownLatch; 71 import java.util.concurrent.TimeUnit; 72 73 /** 74 * Build/Install/Run: 75 * atest WmTests:ScreenshotTests 76 */ 77 @SmallTest 78 @Presubmit 79 public class ScreenshotTests { 80 private static final long WAIT_TIME_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER; 81 private static final int BUFFER_WIDTH = 100; 82 private static final int BUFFER_HEIGHT = 100; 83 84 private final Instrumentation mInstrumentation = getInstrumentation(); 85 @Rule 86 public TestName mTestName = new TestName(); 87 88 @Rule 89 public ActivityTestRule<ScreenshotActivity> mActivityRule = 90 new ActivityTestRule<>(ScreenshotActivity.class); 91 92 private ScreenshotActivity mActivity; 93 94 @Before setup()95 public void setup() { 96 mActivity = mActivityRule.getActivity(); 97 mInstrumentation.waitForIdleSync(); 98 } 99 100 @After tearDown()101 public void tearDown() { 102 CommonUtils.waitUntilActivityRemoved(mActivity); 103 } 104 105 @Test testScreenshotSecureLayers()106 public void testScreenshotSecureLayers() throws InterruptedException { 107 SurfaceControl secureSC = new SurfaceControl.Builder() 108 .setName("SecureChildSurfaceControl") 109 .setBLASTLayer() 110 .setCallsite("makeSecureSurfaceControl") 111 .setSecure(true) 112 .build(); 113 114 SurfaceControl.Transaction t = mActivity.addChildSc(secureSC); 115 mInstrumentation.waitForIdleSync(); 116 117 GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT, 118 PixelFormat.RGBA_8888, 119 GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER 120 | GraphicBuffer.USAGE_SW_WRITE_RARELY); 121 122 Canvas canvas = buffer.lockCanvas(); 123 canvas.drawColor(Color.RED); 124 buffer.unlockCanvasAndPost(canvas); 125 126 CountDownLatch countDownLatch = new CountDownLatch(1); 127 t.show(secureSC) 128 .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer)) 129 .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB) 130 .addTransactionCommittedListener(Runnable::run, countDownLatch::countDown) 131 .apply(); 132 assertTrue("Failed to wait for transaction to get committed", 133 countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS)); 134 assertTrue("Failed to wait for stable geometry", 135 waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S))); 136 137 ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC) 138 .setCaptureSecureLayers(true) 139 .setChildrenOnly(false) 140 .build(); 141 142 ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1]; 143 Bitmap screenshot = null; 144 Bitmap swBitmap = null; 145 try { 146 CountDownLatch screenshotComplete = new CountDownLatch(1); 147 ScreenCapture.captureLayers(args, new ScreenCapture.ScreenCaptureListener( 148 (screenshotHardwareBuffer, result) -> { 149 if (result == 0) { 150 screenCapture[0] = screenshotHardwareBuffer; 151 } 152 screenshotComplete.countDown(); 153 })); 154 assertTrue("Failed to wait for screen capture", 155 screenshotComplete.await(WAIT_TIME_S, TimeUnit.SECONDS)); 156 assertNotNull("Screen capture buffer is null", screenCapture[0]); 157 158 screenshot = screenCapture[0].asBitmap(); 159 assertNotNull("Screenshot from bitmap is null", screenshot); 160 161 swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); 162 163 BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED); 164 Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight()); 165 int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); 166 int sizeOfBitmap = bounds.width() * bounds.height(); 167 168 assertEquals("numMatchingPixels=" + numMatchingPixels + " sizeOfBitmap=" + sizeOfBitmap, 169 sizeOfBitmap, numMatchingPixels); 170 } finally { 171 if (screenshot != null) { 172 screenshot.recycle(); 173 } 174 if (swBitmap != null) { 175 swBitmap.recycle(); 176 } 177 if (screenCapture[0].getHardwareBuffer() != null) { 178 screenCapture[0].getHardwareBuffer().close(); 179 } 180 } 181 } 182 183 @Test testCaptureDisplay()184 public void testCaptureDisplay() throws Exception { 185 IWindowManager windowManager = IWindowManager.Stub.asInterface( 186 ServiceManager.getService(Context.WINDOW_SERVICE)); 187 SurfaceControl sc = new SurfaceControl.Builder() 188 .setName("Layer") 189 .setCallsite("testCaptureDisplay") 190 .build(); 191 192 SurfaceControl.Transaction t = mActivity.addChildSc(sc); 193 mInstrumentation.waitForIdleSync(); 194 195 GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT, 196 PixelFormat.RGBA_8888, 197 GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER 198 | GraphicBuffer.USAGE_SW_WRITE_RARELY); 199 200 Canvas canvas = buffer.lockCanvas(); 201 canvas.drawColor(Color.RED); 202 buffer.unlockCanvasAndPost(canvas); 203 204 Point point = mActivity.getPositionBelowStatusBar(); 205 CountDownLatch countDownLatch = new CountDownLatch(1); 206 t.show(sc) 207 .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer)) 208 .setDataSpace(sc, DataSpace.DATASPACE_SRGB) 209 .setPosition(sc, point.x, point.y) 210 .addTransactionCommittedListener(Runnable::run, countDownLatch::countDown) 211 .apply(); 212 213 assertTrue("Failed to wait for transaction to get committed", 214 countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS)); 215 assertTrue("Failed to wait for stable geometry", 216 waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S))); 217 218 ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1]; 219 Bitmap screenshot = null; 220 Bitmap swBitmap = null; 221 try { 222 CountDownLatch screenshotComplete = new CountDownLatch(1); 223 windowManager.captureDisplay(DEFAULT_DISPLAY, null, 224 new ScreenCapture.ScreenCaptureListener( 225 (screenshotHardwareBuffer, result) -> { 226 if (result == 0) { 227 screenCapture[0] = screenshotHardwareBuffer; 228 } 229 screenshotComplete.countDown(); 230 })); 231 assertTrue("Failed to wait for screen capture", 232 screenshotComplete.await(WAIT_TIME_S, TimeUnit.SECONDS)); 233 assertNotNull("Screen capture buffer is null", screenCapture[0]); 234 235 screenshot = screenCapture[0].asBitmap(); 236 assertNotNull("Screenshot from bitmap is null", screenshot); 237 238 swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); 239 240 BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED); 241 Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, 242 BUFFER_HEIGHT + point.y); 243 int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); 244 int pixelMatchSize = bounds.width() * bounds.height(); 245 boolean success = numMatchingPixels == pixelMatchSize; 246 247 if (!success) { 248 SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage"); 249 } 250 assertTrue( 251 "numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize, 252 success); 253 } finally { 254 if (screenshot != null) { 255 screenshot.recycle(); 256 } 257 if (swBitmap != null) { 258 swBitmap.recycle(); 259 } 260 if (screenCapture[0].getHardwareBuffer() != null) { 261 screenCapture[0].getHardwareBuffer().close(); 262 } 263 } 264 } 265 266 public static class ScreenshotActivity extends Activity { 267 private static final long WAIT_TIMEOUT_S = 5; 268 private final Handler mHandler = new Handler(Looper.getMainLooper()); 269 270 private final CountDownLatch mAttachedLatch = new CountDownLatch(1); 271 272 @Override onCreate(@ullable Bundle savedInstanceState)273 protected void onCreate(@Nullable Bundle savedInstanceState) { 274 super.onCreate(savedInstanceState); 275 getWindow().getDecorView().setPointerIcon( 276 PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); 277 } 278 279 @Override onAttachedToWindow()280 public void onAttachedToWindow() { 281 super.onAttachedToWindow(); 282 mAttachedLatch.countDown(); 283 } 284 addChildSc(SurfaceControl surfaceControl)285 SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) 286 throws InterruptedException { 287 assertTrue("Failed to wait for onAttachedToWindow", 288 mAttachedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 289 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 290 CountDownLatch countDownLatch = new CountDownLatch(1); 291 mHandler.post(() -> { 292 t.merge(getWindow().getRootSurfaceControl().buildReparentTransaction( 293 surfaceControl)); 294 countDownLatch.countDown(); 295 }); 296 297 try { 298 countDownLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 299 } catch (InterruptedException e) { 300 } 301 return t; 302 } 303 getPositionBelowStatusBar()304 public Point getPositionBelowStatusBar() { 305 Insets statusBarInsets = getWindow() 306 .getDecorView() 307 .getRootWindowInsets() 308 .getInsets(statusBars() | displayCutout()); 309 310 return new Point(statusBarInsets.left, statusBarInsets.top); 311 } 312 } 313 } 314