• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 #pragma once
17 
18 #include <android-base/file.h>
19 #include <android/bitmap.h>
20 #include <android/data_space.h>
21 #include <android/imagedecoder.h>
22 #include <gui/AidlUtil.h>
23 #include <gui/SyncScreenCaptureListener.h>
24 #include <private/gui/ComposerServiceAIDL.h>
25 #include <ui/FenceResult.h>
26 #include <ui/PixelFormat.h>
27 #include <ui/Rect.h>
28 #include <utils/String8.h>
29 #include <functional>
30 #include "TransactionUtils.h"
31 
32 #include <filesystem>
33 #include <fstream>
34 
35 namespace android {
36 
37 using gui::aidl_utils::statusTFromBinderStatus;
38 
39 namespace {
40 
41 // A ScreenCapture is a screenshot from SurfaceFlinger that can be used to check
42 // individual pixel values for testing purposes.
43 class ScreenCapture : public RefBase {
44 public:
captureDisplay(DisplayCaptureArgs & captureArgs,ScreenCaptureResults & captureResults)45     static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
46                                    ScreenCaptureResults& captureResults) {
47         const auto sf = ComposerServiceAIDL::getComposerService();
48         SurfaceComposerClient::Transaction().apply(true);
49 
50         captureArgs.captureArgs.dataspace = static_cast<int32_t>(ui::Dataspace::V0_SRGB);
51         const sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
52         binder::Status status = sf->captureDisplay(captureArgs, captureListener);
53         status_t err = statusTFromBinderStatus(status);
54         if (err != NO_ERROR) {
55             return err;
56         }
57         captureResults = captureListener->waitForResults();
58         return fenceStatus(captureResults.fenceResult);
59     }
60 
captureScreen(std::unique_ptr<ScreenCapture> * sc)61     static void captureScreen(std::unique_ptr<ScreenCapture>* sc) {
62         const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
63         // TODO(b/248317436): extend to cover all displays for multi-display devices
64         const auto display =
65                 ids.empty() ? nullptr : SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
66         captureScreen(sc, display);
67     }
68 
captureScreen(std::unique_ptr<ScreenCapture> * sc,sp<IBinder> displayToken)69     static void captureScreen(std::unique_ptr<ScreenCapture>* sc, sp<IBinder> displayToken) {
70         DisplayCaptureArgs args;
71         args.displayToken = displayToken;
72         captureDisplay(sc, args);
73     }
74 
captureDisplay(std::unique_ptr<ScreenCapture> * sc,DisplayCaptureArgs & captureArgs)75     static void captureDisplay(std::unique_ptr<ScreenCapture>* sc,
76                                DisplayCaptureArgs& captureArgs) {
77         ScreenCaptureResults captureResults;
78         ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
79         *sc = std::make_unique<ScreenCapture>(captureResults.buffer,
80                                               captureResults.capturedHdrLayers);
81     }
82 
captureLayers(LayerCaptureArgs & captureArgs,ScreenCaptureResults & captureResults)83     static status_t captureLayers(LayerCaptureArgs& captureArgs,
84                                   ScreenCaptureResults& captureResults) {
85         const auto sf = ComposerServiceAIDL::getComposerService();
86         SurfaceComposerClient::Transaction().apply(true);
87 
88         captureArgs.captureArgs.dataspace = static_cast<int32_t>(ui::Dataspace::V0_SRGB);
89         const sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
90         binder::Status status = sf->captureLayers(captureArgs, captureListener);
91         status_t err = statusTFromBinderStatus(status);
92         if (err != NO_ERROR) {
93             return err;
94         }
95         captureResults = captureListener->waitForResults();
96         return fenceStatus(captureResults.fenceResult);
97     }
98 
captureLayers(std::unique_ptr<ScreenCapture> * sc,LayerCaptureArgs & captureArgs)99     static void captureLayers(std::unique_ptr<ScreenCapture>* sc, LayerCaptureArgs& captureArgs) {
100         ScreenCaptureResults captureResults;
101         ASSERT_EQ(NO_ERROR, captureLayers(captureArgs, captureResults));
102         *sc = std::make_unique<ScreenCapture>(captureResults.buffer,
103                                               captureResults.capturedHdrLayers);
104     }
105 
capturedHdrLayers()106     bool capturedHdrLayers() const { return mContainsHdr; }
107 
108     void expectColor(const Rect& rect, const Color& color, uint8_t tolerance = 0) {
109         ASSERT_NE(nullptr, mOutBuffer);
110         ASSERT_NE(nullptr, mPixels);
111         ASSERT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, mOutBuffer->getPixelFormat());
112         TransactionUtils::expectBufferColor(mOutBuffer, mPixels, rect, color, tolerance);
113     }
114 
115     void expectBorder(const Rect& rect, const Color& color, uint8_t tolerance = 0) {
116         ASSERT_NE(nullptr, mOutBuffer);
117         ASSERT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, mOutBuffer->getPixelFormat());
118         const bool leftBorder = rect.left > 0;
119         const bool topBorder = rect.top > 0;
120         const bool rightBorder = rect.right < int32_t(mOutBuffer->getWidth());
121         const bool bottomBorder = rect.bottom < int32_t(mOutBuffer->getHeight());
122 
123         if (topBorder) {
124             Rect top(rect.left, rect.top - 1, rect.right, rect.top);
125             if (leftBorder) {
126                 top.left -= 1;
127             }
128             if (rightBorder) {
129                 top.right += 1;
130             }
131             expectColor(top, color, tolerance);
132         }
133         if (leftBorder) {
134             Rect left(rect.left - 1, rect.top, rect.left, rect.bottom);
135             expectColor(left, color, tolerance);
136         }
137         if (rightBorder) {
138             Rect right(rect.right, rect.top, rect.right + 1, rect.bottom);
139             expectColor(right, color, tolerance);
140         }
141         if (bottomBorder) {
142             Rect bottom(rect.left, rect.bottom, rect.right, rect.bottom + 1);
143             if (leftBorder) {
144                 bottom.left -= 1;
145             }
146             if (rightBorder) {
147                 bottom.right += 1;
148             }
149             expectColor(bottom, color, tolerance);
150         }
151     }
152 
153     void expectQuadrant(const Rect& rect, const Color& topLeft, const Color& topRight,
154                         const Color& bottomLeft, const Color& bottomRight, bool filtered = false,
155                         uint8_t tolerance = 0) {
156         ASSERT_TRUE((rect.right - rect.left) % 2 == 0 && (rect.bottom - rect.top) % 2 == 0);
157 
158         const int32_t centerX = rect.left + (rect.right - rect.left) / 2;
159         const int32_t centerY = rect.top + (rect.bottom - rect.top) / 2;
160         // avoid checking borders due to unspecified filtering behavior
161         const int32_t offsetX = filtered ? 2 : 0;
162         const int32_t offsetY = filtered ? 2 : 0;
163         expectColor(Rect(rect.left, rect.top, centerX - offsetX, centerY - offsetY), topLeft,
164                     tolerance);
165         expectColor(Rect(centerX + offsetX, rect.top, rect.right, centerY - offsetY), topRight,
166                     tolerance);
167         expectColor(Rect(rect.left, centerY + offsetY, centerX - offsetX, rect.bottom), bottomLeft,
168                     tolerance);
169         expectColor(Rect(centerX + offsetX, centerY + offsetY, rect.right, rect.bottom),
170                     bottomRight, tolerance);
171     }
172 
checkPixel(uint32_t x,uint32_t y,uint8_t r,uint8_t g,uint8_t b)173     void checkPixel(uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b) {
174         ASSERT_NE(nullptr, mOutBuffer);
175         ASSERT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, mOutBuffer->getPixelFormat());
176         const uint8_t* pixel = mPixels + (4 * (y * mOutBuffer->getStride() + x));
177         if (r != pixel[0] || g != pixel[1] || b != pixel[2]) {
178             String8 err(String8::format("pixel @ (%3d, %3d): "
179                                         "expected [%3d, %3d, %3d], got [%3d, %3d, %3d]",
180                                         x, y, r, g, b, pixel[0], pixel[1], pixel[2]));
181             EXPECT_EQ(String8(), err) << err.c_str();
182         }
183     }
184 
writePng(const std::filesystem::path & path,const void * pixels,uint32_t width,uint32_t height,uint32_t stride)185     static void writePng(const std::filesystem::path& path, const void* pixels, uint32_t width,
186                          uint32_t height, uint32_t stride) {
187         AndroidBitmapInfo info{
188                 .width = width,
189                 .height = height,
190                 .stride = stride,
191                 .format = ANDROID_BITMAP_FORMAT_RGBA_8888,
192                 .flags = ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE,
193         };
194 
195         std::ofstream file(path, std::ios::binary);
196         ASSERT_TRUE(file.is_open());
197 
198         auto writeFunc = [](void* filePtr, const void* data, size_t size) -> bool {
199             auto file = reinterpret_cast<std::ofstream*>(filePtr);
200             file->write(reinterpret_cast<const char*>(data), size);
201             return file->good();
202         };
203 
204         int compressResult = AndroidBitmap_compress(&info, ADATASPACE_SRGB, pixels,
205                                                     ANDROID_BITMAP_COMPRESS_FORMAT_PNG,
206                                                     /*(ignored) quality=*/100, &file, writeFunc);
207         ASSERT_EQ(compressResult, ANDROID_BITMAP_RESULT_SUCCESS);
208         file.close();
209     }
210 
readImage(const std::filesystem::path & filename,std::vector<uint8_t> & outBytes,int & outWidth,int & outHeight)211     static void readImage(const std::filesystem::path& filename, std::vector<uint8_t>& outBytes,
212                           int& outWidth, int& outHeight) {
213         std::ifstream file(filename, std::ios::binary | std::ios::ate);
214         ASSERT_TRUE(file.is_open()) << "Failed to open " << filename;
215 
216         size_t fileSize = file.tellg();
217         file.seekg(0, std::ios::beg);
218         std::vector<char> fileData(fileSize);
219         file.read(fileData.data(), fileSize);
220         file.close();
221 
222         AImageDecoder* decoder = nullptr;
223         int createResult = AImageDecoder_createFromBuffer(fileData.data(), fileSize, &decoder);
224 
225         ASSERT_EQ(createResult, ANDROID_IMAGE_DECODER_SUCCESS);
226 
227         const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(decoder);
228         outWidth = AImageDecoderHeaderInfo_getWidth(headerInfo);
229         outHeight = AImageDecoderHeaderInfo_getHeight(headerInfo);
230         int32_t format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(headerInfo);
231         ASSERT_EQ(format, ANDROID_BITMAP_FORMAT_RGBA_8888);
232 
233         size_t stride = outWidth * 4; // Assuming RGBA format
234         size_t bufferSize = stride * outHeight;
235 
236         outBytes.resize(bufferSize);
237         int decodeResult = AImageDecoder_decodeImage(decoder, outBytes.data(), stride, bufferSize);
238         ASSERT_EQ(decodeResult, ANDROID_IMAGE_DECODER_SUCCESS);
239         AImageDecoder_delete(decoder);
240     }
241 
writeGraphicBufferToPng(const std::string & path,const sp<GraphicBuffer> & buffer)242     static void writeGraphicBufferToPng(const std::string& path, const sp<GraphicBuffer>& buffer) {
243         base::unique_fd fd{open(path.c_str(), O_WRONLY | O_CREAT, S_IWUSR)};
244         ASSERT_GE(fd.get(), 0);
245 
246         void* pixels = nullptr;
247         int32_t stride = 0;
248         auto lockStatus = buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &pixels,
249                                        nullptr /*outBytesPerPixel*/, &stride);
250         ASSERT_GE(lockStatus, 0);
251 
252         writePng(path, pixels, buffer->getWidth(), buffer->getHeight(), stride);
253 
254         auto unlockStatus = buffer->unlock();
255         ASSERT_GE(unlockStatus, 0);
256     }
257 
258     // Tries to read an image from executable directory
259     // If the test fails, the screenshot is written to $TMPDIR
expectBufferMatchesImageFromFile(const Rect & rect,const std::filesystem::path & pathRelativeToExeDir)260     void expectBufferMatchesImageFromFile(const Rect& rect,
261                                           const std::filesystem::path& pathRelativeToExeDir) {
262         ASSERT_NE(nullptr, mOutBuffer);
263         ASSERT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, mOutBuffer->getPixelFormat());
264 
265         int bufferWidth = int32_t(mOutBuffer->getWidth());
266         int bufferHeight = int32_t(mOutBuffer->getHeight());
267         int bufferStride = mOutBuffer->getStride() * 4;
268 
269         std::vector<uint8_t> imagePixels;
270         int imageWidth;
271         int imageHeight;
272         readImage(android::base::GetExecutableDirectory() / pathRelativeToExeDir, imagePixels,
273                   imageWidth, imageHeight);
274         int imageStride = 4 * imageWidth;
275 
276         ASSERT_TRUE(rect.isValid());
277 
278         ASSERT_GE(rect.left, 0);
279         ASSERT_GE(rect.bottom, 0);
280 
281         ASSERT_LE(rect.right, bufferWidth);
282         ASSERT_LE(rect.bottom, bufferHeight);
283 
284         ASSERT_LE(rect.right, imageWidth);
285         ASSERT_LE(rect.bottom, imageHeight);
286 
287         int tolerance = 4; // arbitrary
288         for (int32_t y = rect.top; y < rect.bottom; y++) {
289             for (int32_t x = rect.left; x < rect.right; x++) {
290                 const uint8_t* bufferPixel = mPixels + y * bufferStride + x * 4;
291                 const uint8_t* imagePixel =
292                         imagePixels.data() + (y - rect.top) * imageStride + (x - rect.left) * 4;
293 
294                 int dr = bufferPixel[0] - imagePixel[0];
295                 int dg = bufferPixel[1] - imagePixel[1];
296                 int db = bufferPixel[2] - imagePixel[2];
297                 int da = bufferPixel[3] - imagePixel[3];
298                 int dist = std::abs(dr) + std::abs(dg) + std::abs(db) + std::abs(da);
299 
300                 bool pixelMatches = dist < tolerance;
301 
302                 if (!pixelMatches) {
303                     std::filesystem::path outFilename = pathRelativeToExeDir.filename();
304                     outFilename.replace_extension();
305                     outFilename += "_actual.png";
306                     std::filesystem::path outPath = std::filesystem::temp_directory_path() /
307                             "SurfaceFlinger_test_screenshots" / outFilename;
308                     writeGraphicBufferToPng(outPath, mOutBuffer);
309 
310                     ASSERT_TRUE(pixelMatches)
311                             << String8::format("pixel @ (%3d, %3d): "
312                                                "expected [%3d, %3d, %3d, %3d], got [%3d, %3d, %3d, "
313                                                "%3d], "
314                                                "wrote screenshot to '%s'",
315                                                x, y, imagePixel[0], imagePixel[1], imagePixel[2],
316                                                imagePixel[3], bufferPixel[0], bufferPixel[1],
317                                                bufferPixel[2], bufferPixel[3], outPath.c_str())
318                                        .c_str();
319                     return;
320                 }
321             }
322         }
323     }
324 
getPixelColor(uint32_t x,uint32_t y)325     Color getPixelColor(uint32_t x, uint32_t y) {
326         if (!mOutBuffer || mOutBuffer->getPixelFormat() != HAL_PIXEL_FORMAT_RGBA_8888) {
327             return {0, 0, 0, 0};
328         }
329 
330         const uint8_t* pixel = mPixels + (4 * (y * mOutBuffer->getStride() + x));
331         return {pixel[0], pixel[1], pixel[2], pixel[3]};
332     }
333 
expectFGColor(uint32_t x,uint32_t y)334     void expectFGColor(uint32_t x, uint32_t y) { checkPixel(x, y, 195, 63, 63); }
335 
expectBGColor(uint32_t x,uint32_t y)336     void expectBGColor(uint32_t x, uint32_t y) { checkPixel(x, y, 63, 63, 195); }
337 
expectChildColor(uint32_t x,uint32_t y)338     void expectChildColor(uint32_t x, uint32_t y) { checkPixel(x, y, 200, 200, 200); }
339 
expectSize(uint32_t width,uint32_t height)340     void expectSize(uint32_t width, uint32_t height) {
341         EXPECT_EQ(width, mOutBuffer->getWidth());
342         EXPECT_EQ(height, mOutBuffer->getHeight());
343     }
344 
ScreenCapture(const sp<GraphicBuffer> & outBuffer,bool containsHdr)345     explicit ScreenCapture(const sp<GraphicBuffer>& outBuffer, bool containsHdr)
346           : mOutBuffer(outBuffer), mContainsHdr(containsHdr) {
347         if (mOutBuffer) {
348             mOutBuffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, reinterpret_cast<void**>(&mPixels));
349         }
350     }
351 
~ScreenCapture()352     ~ScreenCapture() {
353         if (mOutBuffer) mOutBuffer->unlock();
354     }
355 
356 private:
357     sp<GraphicBuffer> mOutBuffer;
358     bool mContainsHdr = mContainsHdr;
359     uint8_t* mPixels = nullptr;
360 };
361 } // namespace
362 } // namespace android
363