| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wconversion" |
| #pragma clang diagnostic ignored "-Wextra" |
| |
| #include <chrono> |
| |
| #include <android/native_window.h> |
| #include <binder/IPCThreadState.h> |
| #include <gtest/gtest.h> |
| #include <gui/Surface.h> |
| #include <gui/SurfaceComposerClient.h> |
| #include <private/gui/ComposerService.h> |
| #include <ui/GraphicBuffer.h> |
| #include <ui/Rect.h> |
| |
| #include "ColorUtils.h" |
| |
| namespace android { |
| |
| namespace { |
| |
| using namespace std::chrono_literals; |
| using Transaction = SurfaceComposerClient::Transaction; |
| |
| std::ostream& operator<<(std::ostream& os, const Color& color) { |
| os << int(color.r) << ", " << int(color.g) << ", " << int(color.b) << ", " << int(color.a); |
| return os; |
| } |
| |
| class TransactionUtils { |
| public: |
| // Fill a region with the specified color. |
| static void fillANativeWindowBufferColor(const ANativeWindow_Buffer& buffer, const Rect& rect, |
| const Color& color) { |
| Rect r(0, 0, buffer.width, buffer.height); |
| if (!r.intersect(rect, &r)) { |
| return; |
| } |
| |
| int32_t width = r.right - r.left; |
| int32_t height = r.bottom - r.top; |
| |
| for (int32_t row = 0; row < height; row++) { |
| uint8_t* dst = static_cast<uint8_t*>(buffer.bits) + |
| (buffer.stride * (r.top + row) + r.left) * 4; |
| for (int32_t column = 0; column < width; column++) { |
| dst[0] = color.r; |
| dst[1] = color.g; |
| dst[2] = color.b; |
| dst[3] = color.a; |
| dst += 4; |
| } |
| } |
| } |
| |
| // Fill a region with the specified color. |
| static void fillGraphicBufferColor(const sp<GraphicBuffer>& buffer, const Rect& rect, |
| const Color& color) { |
| Rect r(0, 0, buffer->width, buffer->height); |
| if (!r.intersect(rect, &r)) { |
| return; |
| } |
| |
| int32_t width = r.right - r.left; |
| int32_t height = r.bottom - r.top; |
| |
| uint8_t* pixels; |
| buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, |
| reinterpret_cast<void**>(&pixels)); |
| |
| for (int32_t row = 0; row < height; row++) { |
| uint8_t* dst = pixels + (buffer->getStride() * (r.top + row) + r.left) * 4; |
| for (int32_t column = 0; column < width; column++) { |
| dst[0] = color.r; |
| dst[1] = color.g; |
| dst[2] = color.b; |
| dst[3] = color.a; |
| dst += 4; |
| } |
| } |
| buffer->unlock(); |
| } |
| |
| // Check if a region has the specified color. |
| static void expectBufferColor(const sp<GraphicBuffer>& outBuffer, uint8_t* pixels, |
| const Rect& rect, const Color& color, uint8_t tolerance) { |
| int32_t x = rect.left; |
| int32_t y = rect.top; |
| int32_t width = rect.right - rect.left; |
| int32_t height = rect.bottom - rect.top; |
| |
| int32_t bufferWidth = int32_t(outBuffer->getWidth()); |
| int32_t bufferHeight = int32_t(outBuffer->getHeight()); |
| if (x + width > bufferWidth) { |
| x = std::min(x, bufferWidth); |
| width = bufferWidth - x; |
| } |
| if (y + height > bufferHeight) { |
| y = std::min(y, bufferHeight); |
| height = bufferHeight - y; |
| } |
| |
| auto colorCompare = [tolerance](uint8_t a, uint8_t b) { |
| uint8_t tmp = a >= b ? a - b : b - a; |
| return tmp <= tolerance; |
| }; |
| for (int32_t j = 0; j < height; j++) { |
| const uint8_t* src = pixels + (outBuffer->getStride() * (y + j) + x) * 4; |
| for (int32_t i = 0; i < width; i++) { |
| const uint8_t expected[4] = {color.r, color.g, color.b, color.a}; |
| ASSERT_TRUE(std::equal(src, src + 4, expected, colorCompare)) |
| << "pixel @ (" << x + i << ", " << y + j << "): " |
| << "expected (" << color << "), " |
| << "got (" << Color{src[0], src[1], src[2], src[3]} << ")"; |
| src += 4; |
| } |
| } |
| } |
| |
| static void fillSurfaceRGBA8(const sp<SurfaceControl>& sc, const Color& color, |
| bool unlock = true) { |
| fillSurfaceRGBA8(sc, color.r, color.g, color.b, unlock); |
| } |
| |
| // Fill an RGBA_8888 formatted surface with a single color. |
| static void fillSurfaceRGBA8(const sp<SurfaceControl>& sc, uint8_t r, uint8_t g, uint8_t b, |
| bool unlock = true) { |
| ANativeWindow_Buffer outBuffer; |
| sp<Surface> s = sc->getSurface(); |
| ASSERT_TRUE(s != nullptr); |
| ASSERT_EQ(NO_ERROR, s->lock(&outBuffer, nullptr)); |
| uint8_t* img = reinterpret_cast<uint8_t*>(outBuffer.bits); |
| for (int y = 0; y < outBuffer.height; y++) { |
| for (int x = 0; x < outBuffer.width; x++) { |
| uint8_t* pixel = img + (4 * (y * outBuffer.stride + x)); |
| pixel[0] = r; |
| pixel[1] = g; |
| pixel[2] = b; |
| pixel[3] = 255; |
| } |
| } |
| if (unlock) { |
| ASSERT_EQ(NO_ERROR, s->unlockAndPost()); |
| } |
| } |
| |
| static void setFrame(Transaction& t, const sp<SurfaceControl>& sc, Rect source, Rect dest, |
| int32_t transform = 0) { |
| uint32_t sourceWidth = source.getWidth(); |
| uint32_t sourceHeight = source.getHeight(); |
| |
| if (transform & ui::Transform::ROT_90) { |
| std::swap(sourceWidth, sourceHeight); |
| } |
| |
| float dsdx = dest.getWidth() / static_cast<float>(sourceWidth); |
| float dsdy = dest.getHeight() / static_cast<float>(sourceHeight); |
| |
| t.setMatrix(sc, dsdx, 0, 0, dsdy); |
| t.setPosition(sc, dest.left, dest.top); |
| } |
| }; |
| |
| enum class RenderPath { SCREENSHOT, VIRTUAL_DISPLAY }; |
| |
| // Environment for starting up binder threads. This is required for testing |
| // virtual displays, as BufferQueue parameters may be queried over binder. |
| class BinderEnvironment : public ::testing::Environment { |
| public: |
| void SetUp() override { ProcessState::self()->startThreadPool(); } |
| }; |
| |
| /** RAII Wrapper around get/seteuid */ |
| class UIDFaker { |
| uid_t oldId; |
| |
| public: |
| UIDFaker(uid_t uid) { |
| oldId = geteuid(); |
| seteuid(uid); |
| } |
| ~UIDFaker() { seteuid(oldId); } |
| }; |
| } // namespace |
| } // namespace android |
| |
| // TODO(b/129481165): remove the #pragma below and fix conversion issues |
| #pragma clang diagnostic pop // ignored "-Wconversion -Wextra" |