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