• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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