Add border API to surface control
See go/sf-box-shadows-api for more details
Bug: b/367464660
Flag: com.android.window.flags.enable_border_settings
Test: atest SurfaceFlinger_test
Change-Id: I1190edb97693004d9f46058fd0165451470a65b3
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index fb8fed0..34b0bb5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -18,6 +18,7 @@
#include <cstdint>
+#include <android/gui/BorderSettings.h>
#include <android/gui/CachingHint.h>
#include <gui/DisplayLuts.h>
#include <gui/HdrMetadata.h>
@@ -141,6 +142,9 @@
ShadowSettings shadowSettings;
+ // The settings to configure the outline of a layer.
+ gui::BorderSettings borderSettings;
+
// List of regions that require blur
std::vector<BlurRegion> blurRegions;
diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
index 348111d..294b167 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -70,6 +70,9 @@
out.append(" ");
dumpVal(out, "shadowLength", shadowSettings.length);
+ out.append(" ");
+ dumpVal(out, "borderSettings", borderSettings.toString());
+
out.append("\n ");
dumpVal(out, "blend", toString(blendMode), blendMode);
dumpVal(out, "alpha", alpha);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index ea36011..e4793a4 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -237,6 +237,16 @@
geomLayerBounds.bottom += outset;
}
+ // Similar to above
+ if (layerState.forceClientComposition && layerState.borderSettings.strokeWidth > 0.0f) {
+ // Antialiasing should never add more than 2 pixels.
+ const auto outset = layerState.borderSettings.strokeWidth + 2;
+ geomLayerBounds.left -= outset;
+ geomLayerBounds.top -= outset;
+ geomLayerBounds.right += outset;
+ geomLayerBounds.bottom += outset;
+ }
+
geomLayerBounds = layerTransform.transform(geomLayerBounds);
FloatRect frame = reduce(geomLayerBounds, activeTransparentRegion);
frame = frame.intersect(outputState.layerStackSpace.getContent().toFloatRect());
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index ca262ee..2f531f1 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -355,6 +355,26 @@
EXPECT_THAT(calculateOutputDisplayFrame(), expected);
}
+TEST_F(OutputLayerDisplayFrameTest, outlineExpandsDisplayFrame) {
+ const int kStrokeWidth = 3;
+ mLayerFEState.borderSettings.strokeWidth = kStrokeWidth;
+ mLayerFEState.forceClientComposition = true;
+
+ mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
+ Rect expected{mLayerFEState.geomLayerBounds};
+ expected.inset(-kStrokeWidth - 2, -kStrokeWidth - 2, -kStrokeWidth - 2, -kStrokeWidth - 2);
+ EXPECT_THAT(calculateOutputDisplayFrame(), expected);
+}
+TEST_F(OutputLayerDisplayFrameTest, outlineExpandsDisplayFrame_onlyIfForcingClientComposition) {
+ const int kStrokeWidth = 3;
+ mLayerFEState.borderSettings.strokeWidth = kStrokeWidth;
+ mLayerFEState.forceClientComposition = false;
+
+ mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
+ Rect expected{mLayerFEState.geomLayerBounds};
+ EXPECT_THAT(calculateOutputDisplayFrame(), expected);
+}
+
/*
* OutputLayer::calculateOutputRelativeBufferTransform()
*/
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index 964a970..3aa2e98 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -179,8 +179,12 @@
return backgroundBlurRadius > 0 || blurRegions.size() > 0;
}
+bool LayerSnapshot::hasOutline() const {
+ return borderSettings.strokeWidth > 0;
+}
+
bool LayerSnapshot::hasEffect() const {
- return fillsColor() || drawShadows() || hasBlur();
+ return fillsColor() || drawShadows() || hasBlur() || hasOutline();
}
bool LayerSnapshot::hasSomethingToDraw() const {
@@ -253,6 +257,7 @@
reason << " buffer=" << externalTexture->getId() << " frame=" << frameNumber;
if (fillsColor() || color.a > 0.0f) reason << " color{" << color << "}";
if (drawShadows()) reason << " shadowSettings.length=" << shadowSettings.length;
+ if (hasOutline()) reason << "borderSettings=" << borderSettings.toString();
if (backgroundBlurRadius > 0) reason << " backgroundBlurRadius=" << backgroundBlurRadius;
if (blurRegions.size() > 0) reason << " blurRegions.size()=" << blurRegions.size();
if (contentDirty) reason << " contentDirty";
@@ -410,7 +415,9 @@
if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged) {
shadowSettings.length = requested.shadowRadius;
}
-
+ if (forceUpdate || requested.what & layer_state_t::eBorderSettingsChanged) {
+ borderSettings = requested.borderSettings;
+ }
if (forceUpdate || requested.what & layer_state_t::eFrameRateSelectionPriority) {
frameRateSelectionPriority = requested.frameRateSelectionPriority;
}
@@ -508,9 +515,9 @@
(layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
layer_state_t::eApiChanged | layer_state_t::eShadowRadiusChanged |
layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged |
- layer_state_t::eEdgeExtensionChanged)) {
+ layer_state_t::eEdgeExtensionChanged | layer_state_t::eBorderSettingsChanged)) {
forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect() ||
- edgeExtensionEffect.hasEffect();
+ edgeExtensionEffect.hasEffect() || borderSettings.strokeWidth > 0;
}
if (forceUpdate ||
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 69120bd..eca9718 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -149,6 +149,7 @@
bool hasBlur() const;
bool hasBufferOrSidebandStream() const;
bool hasEffect() const;
+ bool hasOutline() const;
bool hasSomethingToDraw() const;
bool isContentOpaque() const;
bool isHiddenByPolicy() const;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 28a6031..91b54af 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -939,6 +939,18 @@
}
if (forceUpdate ||
+ snapshot.clientChanges &
+ (layer_state_t::eBorderSettingsChanged | layer_state_t::eAlphaChanged)) {
+ snapshot.borderSettings = requested.borderSettings;
+
+ // Multiply outline alpha by snapshot alpha.
+ uint32_t c = static_cast<uint32_t>(snapshot.borderSettings.color);
+ float alpha = snapshot.alpha * (c >> 24) / 255.0f;
+ uint32_t a = static_cast<uint32_t>(alpha * 255 + 0.5f);
+ snapshot.borderSettings.color = static_cast<int32_t>((c & ~0xff000000) | (a << 24));
+ }
+
+ if (forceUpdate ||
snapshot.changes.any(RequestedLayerState::Changes::Geometry |
RequestedLayerState::Changes::Input)) {
updateInput(snapshot, requested, parentSnapshot, path, args);
@@ -946,7 +958,9 @@
// computed snapshot properties
snapshot.forceClientComposition = snapshot.shadowSettings.length > 0 ||
- snapshot.stretchEffect.hasEffect() || snapshot.edgeExtensionEffect.hasEffect();
+ snapshot.stretchEffect.hasEffect() || snapshot.edgeExtensionEffect.hasEffect() ||
+ snapshot.borderSettings.strokeWidth > 0;
+
snapshot.contentOpaque = snapshot.isContentOpaque();
snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() &&
snapshot.color.a == 1.f;
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 5e076bd..3cd432c 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -113,6 +113,8 @@
// set the shadow for the layer if needed
prepareShadowClientComposition(*layerSettings, targetSettings.viewport);
+ layerSettings->borderSettings = mSnapshot->borderSettings;
+
return layerSettings;
}
@@ -120,6 +122,7 @@
compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
SFTRACE_CALL();
compositionengine::LayerFE::LayerSettings layerSettings;
+ layerSettings.geometry.originalBounds = mSnapshot->geomLayerBounds;
layerSettings.geometry.boundaries =
reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint);
layerSettings.geometry.positionTransform = mSnapshot->geomLayerTransform.asMatrix4();
@@ -205,7 +208,7 @@
if (targetSettings.realContentIsVisible && fillsColor()) {
// Set color for color fill settings.
layerSettings.source.solidColor = mSnapshot->color.rgb;
- } else if (hasBlur() || drawShadows()) {
+ } else if (hasBlur() || drawShadows() || hasOutline()) {
layerSettings.skipContentDraw = true;
}
}
@@ -392,6 +395,10 @@
return mSnapshot->backgroundBlurRadius > 0 || mSnapshot->blurRegions.size() > 0;
}
+bool LayerFE::hasOutline() const {
+ return mSnapshot->borderSettings.strokeWidth > 0;
+}
+
bool LayerFE::drawShadows() const {
return mSnapshot->shadowSettings.length > 0.f &&
(mSnapshot->shadowSettings.ambientColor.a > 0 ||
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index b89b6b4..b897a90 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -83,12 +83,13 @@
compositionengine::LayerFE::LayerSettings&,
compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
- bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); }
+ bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur() || hasOutline(); }
bool hasBufferOrSidebandStream() const;
bool fillsColor() const;
bool hasBlur() const;
bool drawShadows() const;
+ bool hasOutline() const;
const sp<GraphicBuffer> getBuffer() const;
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index 13f6577..c68513e 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -22,6 +22,7 @@
],
static_libs: [
"librenderengine_includes",
+ "libgui_window_info_static",
],
srcs: [
"FlagManager.cpp",
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index b5f7a74..37f3aa7 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -63,7 +63,10 @@
"VirtualDisplay_test.cpp",
"WindowInfosListener_test.cpp",
],
- data: ["SurfaceFlinger_test.filter"],
+ data: [
+ "SurfaceFlinger_test.filter",
+ "testdata/*",
+ ],
static_libs: [
"android.hardware.graphics.composer@2.1",
"libsurfaceflinger_common",
@@ -76,6 +79,7 @@
"libcutils",
"libEGL",
"libGLESv2",
+ "libjnigraphics",
"libgui",
"liblog",
"libnativewindow",
@@ -83,6 +87,7 @@
"libui",
"libutils",
"server_configurable_flags",
+ "libc++",
],
header_libs: [
"libnativewindow_headers",
diff --git a/services/surfaceflinger/tests/AndroidTest.xml b/services/surfaceflinger/tests/AndroidTest.xml
index ad43cdc..b199ddb 100644
--- a/services/surfaceflinger/tests/AndroidTest.xml
+++ b/services/surfaceflinger/tests/AndroidTest.xml
@@ -14,6 +14,11 @@
limitations under the License.
-->
<configuration description="Config for SurfaceFlinger_test">
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="throw-if-cmd-fail" value="true" />
+ <option name="run-command" value="mkdir -p /data/local/tmp/SurfaceFlinger_test_screenshots" />
+ <option name="teardown-command" value="rm -fr /data/local/tmp/SurfaceFlinger_test_screenshots"/>
+ </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="SurfaceFlinger_test->/data/local/tmp/SurfaceFlinger_test" />
@@ -27,4 +32,8 @@
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="SurfaceFlinger_test" />
</test>
-</configuration>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name = "pull-pattern-keys" value = ".*png" />
+ <option name = "directory-keys" value = "/data/local/tmp/SurfaceFlinger_test_screenshots" />
+ </metrics_collector>
+</configuration>
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
index 151611c..ada9862 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -662,6 +662,93 @@
}
}
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBorderSettings) {
+ sp<SurfaceControl> parent;
+ sp<SurfaceControl> child;
+ const uint32_t size = 64;
+ const uint32_t parentSize = size * 3;
+ ASSERT_NO_FATAL_FAILURE(parent = createLayer("parent", parentSize, parentSize));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(parent, Color::RED, parentSize, parentSize));
+ ASSERT_NO_FATAL_FAILURE(child = createLayer("child", size, size));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(child, Color::GREEN, size, size));
+
+ gui::BorderSettings outline;
+ outline.strokeWidth = 3;
+ outline.color = 0xff0000ff;
+ Transaction()
+ .setCrop(parent, Rect(0, 0, parentSize, parentSize))
+ .reparent(child, parent)
+ .setPosition(child, size, size)
+ .setCornerRadius(child, 20.0f)
+ .setBorderSettings(child, outline)
+ .apply(true);
+
+ {
+ auto shot = getScreenCapture();
+
+ shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize),
+ "testdata/SetBorderSettings_Opaque.png");
+ }
+
+ {
+ Transaction().setAlpha(child, 0.5f).apply(true);
+ auto shot = getScreenCapture();
+
+ shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize),
+ "testdata/SetBorderSettings_HalfAlpha.png");
+ }
+
+ {
+ Transaction().setAlpha(child, 0.0f).apply(true);
+
+ auto shot = getScreenCapture();
+
+ shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize),
+ "testdata/SetBorderSettings_ZeroAlpha.png");
+ }
+
+ {
+ Transaction()
+ .setAlpha(child, 1.0f)
+ .setCrop(parent, Rect(0, 0, parentSize / 2, parentSize))
+ .apply(true);
+
+ auto shot = getScreenCapture();
+
+ shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize),
+ "testdata/SetBorderSettings_Cropped.png");
+ }
+
+ {
+ outline.color = 0xff0000ff;
+ outline.strokeWidth = 1;
+ Transaction()
+ .setCrop(parent, Rect(0, 0, parentSize, parentSize))
+ .setBorderSettings(child, outline)
+ .apply(true);
+
+ auto shot = getScreenCapture();
+
+ shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize),
+ "testdata/SetBorderSettings_StrokeWidth1.png");
+ }
+
+ {
+ outline.color = 0x440000ff;
+ outline.strokeWidth = 3;
+ Transaction()
+ .setCrop(parent, Rect(0, 0, parentSize, parentSize))
+ .setBorderSettings(child, outline)
+ .apply(true);
+
+ auto shot = getScreenCapture();
+
+ shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize),
+ "testdata/"
+ "SetBorderSettings_StrokeColorWithAlpha.png");
+ }
+}
+
TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBackgroundBlurRadiusSimple) {
if (!deviceSupportsBlurs()) GTEST_SKIP();
if (!deviceUsesSkiaRenderEngine()) GTEST_SKIP();
diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
index 82390ac..1bee27b 100644
--- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -504,6 +504,17 @@
mLifecycleManager.applyTransactions(transactions);
}
+ void setBorderSettings(uint32_t id, gui::BorderSettings settings) {
+ std::vector<QueuedTransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eBorderSettingsChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.borderSettings = settings;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
void setTrustedOverlay(uint32_t id, gui::TrustedOverlay trustedOverlay) {
std::vector<QueuedTransactionState> transactions;
transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_Cropped.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_Cropped.png
new file mode 100644
index 0000000..b52d517
--- /dev/null
+++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_Cropped.png
Binary files differ
diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_HalfAlpha.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_HalfAlpha.png
new file mode 100644
index 0000000..e1ab54b
--- /dev/null
+++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_HalfAlpha.png
Binary files differ
diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_Opaque.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_Opaque.png
new file mode 100644
index 0000000..bbaf0af
--- /dev/null
+++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_Opaque.png
Binary files differ
diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeColorWithAlpha.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeColorWithAlpha.png
new file mode 100644
index 0000000..0fe2ed8
--- /dev/null
+++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeColorWithAlpha.png
Binary files differ
diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeWidth1.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeWidth1.png
new file mode 100644
index 0000000..3ee5ac6
--- /dev/null
+++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeWidth1.png
Binary files differ
diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_ZeroAlpha.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_ZeroAlpha.png
new file mode 100644
index 0000000..e5e8850
--- /dev/null
+++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_ZeroAlpha.png
Binary files differ
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 07356b9..d045eb8 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1546,6 +1546,14 @@
EXPECT_EQ(getSnapshot(1)->shadowSettings.length, SHADOW_RADIUS);
}
+TEST_F(LayerSnapshotTest, setBorderSettings) {
+ gui::BorderSettings settings;
+ settings.strokeWidth = 5;
+ setBorderSettings(1, settings);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->borderSettings.strokeWidth, settings.strokeWidth);
+}
+
TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) {
hideLayer(1);
setTrustedOverlay(1, gui::TrustedOverlay::ENABLED);
diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
index 0bedcd1..02c3ecd 100644
--- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h
+++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
@@ -15,15 +15,23 @@
*/
#pragma once
+#include <android-base/file.h>
+#include <android/bitmap.h>
+#include <android/data_space.h>
+#include <android/imagedecoder.h>
#include <gui/AidlUtil.h>
#include <gui/SyncScreenCaptureListener.h>
#include <private/gui/ComposerServiceAIDL.h>
#include <ui/FenceResult.h>
+#include <ui/PixelFormat.h>
#include <ui/Rect.h>
#include <utils/String8.h>
#include <functional>
#include "TransactionUtils.h"
+#include <filesystem>
+#include <fstream>
+
namespace android {
using gui::aidl_utils::statusTFromBinderStatus;
@@ -174,6 +182,146 @@
}
}
+ static void writePng(const std::filesystem::path& path, const void* pixels, uint32_t width,
+ uint32_t height, uint32_t stride) {
+ AndroidBitmapInfo info{
+ .width = width,
+ .height = height,
+ .stride = stride,
+ .format = ANDROID_BITMAP_FORMAT_RGBA_8888,
+ .flags = ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE,
+ };
+
+ std::ofstream file(path, std::ios::binary);
+ ASSERT_TRUE(file.is_open());
+
+ auto writeFunc = [](void* filePtr, const void* data, size_t size) -> bool {
+ auto file = reinterpret_cast<std::ofstream*>(filePtr);
+ file->write(reinterpret_cast<const char*>(data), size);
+ return file->good();
+ };
+
+ int compressResult = AndroidBitmap_compress(&info, ADATASPACE_SRGB, pixels,
+ ANDROID_BITMAP_COMPRESS_FORMAT_PNG,
+ /*(ignored) quality=*/100, &file, writeFunc);
+ ASSERT_EQ(compressResult, ANDROID_BITMAP_RESULT_SUCCESS);
+ file.close();
+ }
+
+ static void readImage(const std::filesystem::path& filename, std::vector<uint8_t>& outBytes,
+ int& outWidth, int& outHeight) {
+ std::ifstream file(filename, std::ios::binary | std::ios::ate);
+ ASSERT_TRUE(file.is_open()) << "Failed to open " << filename;
+
+ size_t fileSize = file.tellg();
+ file.seekg(0, std::ios::beg);
+ std::vector<char> fileData(fileSize);
+ file.read(fileData.data(), fileSize);
+ file.close();
+
+ AImageDecoder* decoder = nullptr;
+ int createResult = AImageDecoder_createFromBuffer(fileData.data(), fileSize, &decoder);
+
+ ASSERT_EQ(createResult, ANDROID_IMAGE_DECODER_SUCCESS);
+
+ const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(decoder);
+ outWidth = AImageDecoderHeaderInfo_getWidth(headerInfo);
+ outHeight = AImageDecoderHeaderInfo_getHeight(headerInfo);
+ int32_t format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(headerInfo);
+ ASSERT_EQ(format, ANDROID_BITMAP_FORMAT_RGBA_8888);
+
+ size_t stride = outWidth * 4; // Assuming RGBA format
+ size_t bufferSize = stride * outHeight;
+
+ outBytes.resize(bufferSize);
+ int decodeResult = AImageDecoder_decodeImage(decoder, outBytes.data(), stride, bufferSize);
+ ASSERT_EQ(decodeResult, ANDROID_IMAGE_DECODER_SUCCESS);
+ AImageDecoder_delete(decoder);
+ }
+
+ static void writeGraphicBufferToPng(const std::string& path, const sp<GraphicBuffer>& buffer) {
+ base::unique_fd fd{open(path.c_str(), O_WRONLY | O_CREAT, S_IWUSR)};
+ ASSERT_GE(fd.get(), 0);
+
+ void* pixels = nullptr;
+ int32_t stride = 0;
+ auto lockStatus = buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &pixels,
+ nullptr /*outBytesPerPixel*/, &stride);
+ ASSERT_GE(lockStatus, 0);
+
+ writePng(path, pixels, buffer->getWidth(), buffer->getHeight(), stride);
+
+ auto unlockStatus = buffer->unlock();
+ ASSERT_GE(unlockStatus, 0);
+ }
+
+ // Tries to read an image from executable directory
+ // If the test fails, the screenshot is written to $TMPDIR
+ void expectBufferMatchesImageFromFile(const Rect& rect,
+ const std::filesystem::path& pathRelativeToExeDir) {
+ ASSERT_NE(nullptr, mOutBuffer);
+ ASSERT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, mOutBuffer->getPixelFormat());
+
+ int bufferWidth = int32_t(mOutBuffer->getWidth());
+ int bufferHeight = int32_t(mOutBuffer->getHeight());
+ int bufferStride = mOutBuffer->getStride() * 4;
+
+ std::vector<uint8_t> imagePixels;
+ int imageWidth;
+ int imageHeight;
+ readImage(android::base::GetExecutableDirectory() / pathRelativeToExeDir, imagePixels,
+ imageWidth, imageHeight);
+ int imageStride = 4 * imageWidth;
+
+ ASSERT_TRUE(rect.isValid());
+
+ ASSERT_GE(rect.left, 0);
+ ASSERT_GE(rect.bottom, 0);
+
+ ASSERT_LE(rect.right, bufferWidth);
+ ASSERT_LE(rect.bottom, bufferHeight);
+
+ ASSERT_LE(rect.right, imageWidth);
+ ASSERT_LE(rect.bottom, imageHeight);
+
+ int tolerance = 4; // arbitrary
+ for (int32_t y = rect.top; y < rect.bottom; y++) {
+ for (int32_t x = rect.left; x < rect.right; x++) {
+ const uint8_t* bufferPixel = mPixels + y * bufferStride + x * 4;
+ const uint8_t* imagePixel =
+ imagePixels.data() + (y - rect.top) * imageStride + (x - rect.left) * 4;
+
+ int dr = bufferPixel[0] - imagePixel[0];
+ int dg = bufferPixel[1] - imagePixel[1];
+ int db = bufferPixel[2] - imagePixel[2];
+ int da = bufferPixel[3] - imagePixel[3];
+ int dist = std::abs(dr) + std::abs(dg) + std::abs(db) + std::abs(da);
+
+ bool pixelMatches = dist < tolerance;
+
+ if (!pixelMatches) {
+ std::filesystem::path outFilename = pathRelativeToExeDir.filename();
+ outFilename.replace_extension();
+ outFilename += "_actual.png";
+ std::filesystem::path outPath = std::filesystem::temp_directory_path() /
+ "SurfaceFlinger_test_screenshots" / outFilename;
+ writeGraphicBufferToPng(outPath, mOutBuffer);
+
+ ASSERT_TRUE(pixelMatches)
+ << String8::format("pixel @ (%3d, %3d): "
+ "expected [%3d, %3d, %3d, %3d], got [%3d, %3d, %3d, "
+ "%3d], "
+ "wrote screenshot to '%s'",
+ x, y, imagePixel[0], imagePixel[1], imagePixel[2],
+ imagePixel[3], bufferPixel[0], bufferPixel[1],
+ bufferPixel[2], bufferPixel[3], outPath.c_str())
+ .c_str();
+ return;
+ }
+ }
+ }
+ }
+
Color getPixelColor(uint32_t x, uint32_t y) {
if (!mOutBuffer || mOutBuffer->getPixelFormat() != HAL_PIXEL_FORMAT_RGBA_8888) {
return {0, 0, 0, 0};