summaryrefslogtreecommitdiff
path: root/services/surfaceflinger
diff options
context:
space:
mode:
author Android Build Coastguard Worker <android-build-coastguard-worker@google.com> 2025-03-24 18:39:51 -0700
committer Android Build Coastguard Worker <android-build-coastguard-worker@google.com> 2025-03-24 18:39:51 -0700
commit3115163545c2f5b5857133e2d36c3230c04e93d4 (patch)
tree5b957180779cdb1f93d179daf674e52dceb7fcc1 /services/surfaceflinger
parent386a6107ffa791b211711f4bee75daf05cb87cb6 (diff)
parente20c771af74eddef569070fe9940fe845b769a30 (diff)
Snap for 13264465 from e20c771af74eddef569070fe9940fe845b769a30 to 25Q2-release
Change-Id: Ic34a235cf44331131a19f1f80d1cbaf0b915eaec
Diffstat (limited to 'services/surfaceflinger')
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h4
-rw-r--r--services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp3
-rw-r--r--services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp10
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp20
-rw-r--r--services/surfaceflinger/FrontEnd/LayerSnapshot.cpp15
-rw-r--r--services/surfaceflinger/FrontEnd/LayerSnapshot.h1
-rw-r--r--services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp16
-rw-r--r--services/surfaceflinger/LayerFE.cpp9
-rw-r--r--services/surfaceflinger/LayerFE.h3
-rw-r--r--services/surfaceflinger/SurfaceFlinger.cpp2
-rw-r--r--services/surfaceflinger/common/Android.bp1
-rw-r--r--services/surfaceflinger/tests/Android.bp7
-rw-r--r--services/surfaceflinger/tests/AndroidTest.xml11
-rw-r--r--services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp87
-rw-r--r--services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h11
-rw-r--r--services/surfaceflinger/tests/testdata/SetBorderSettings_Cropped.pngbin0 -> 5603 bytes
-rw-r--r--services/surfaceflinger/tests/testdata/SetBorderSettings_HalfAlpha.pngbin0 -> 6139 bytes
-rw-r--r--services/surfaceflinger/tests/testdata/SetBorderSettings_Opaque.pngbin0 -> 6382 bytes
-rw-r--r--services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeColorWithAlpha.pngbin0 -> 8948 bytes
-rw-r--r--services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeWidth1.pngbin0 -> 8553 bytes
-rw-r--r--services/surfaceflinger/tests/testdata/SetBorderSettings_ZeroAlpha.pngbin0 -> 4569 bytes
-rw-r--r--services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp8
-rw-r--r--services/surfaceflinger/tests/utils/ScreenshotUtils.h148
23 files changed, 346 insertions, 10 deletions
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index fb8fed0743..34b0bb5653 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 @@ struct LayerFECompositionState {
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 348111d06e..294b167d4a 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -70,6 +70,9 @@ void LayerFECompositionState::dump(std::string& out) const {
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 ea360115c1..e4793a4cf2 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -237,6 +237,16 @@ Rect OutputLayer::calculateOutputDisplayFrame() const {
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 ca262ee16a..2f531f11fe 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -355,6 +355,26 @@ TEST_F(OutputLayerDisplayFrameTest, shadowExpandsDisplayFrame_onlyIfForcingClien
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 964a9703b3..3aa2e98e8f 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -179,8 +179,12 @@ bool LayerSnapshot::hasBlur() const {
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 @@ std::string LayerSnapshot::getIsVisibleReason() const {
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 @@ void LayerSnapshot::merge(const RequestedLayerState& requested, bool forceUpdate
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 @@ void LayerSnapshot::merge(const RequestedLayerState& requested, bool forceUpdate
(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 69120bdcff..eca9718c0c 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -149,6 +149,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState {
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 50ed72de9e..e3526d9de0 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -936,6 +936,18 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a
}
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);
@@ -943,7 +955,9 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a
// 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 5e076bdae4..3cd432cd2b 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -113,6 +113,8 @@ std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientC
// set the shadow for the layer if needed
prepareShadowClientComposition(*layerSettings, targetSettings.viewport);
+ layerSettings->borderSettings = mSnapshot->borderSettings;
+
return layerSettings;
}
@@ -120,6 +122,7 @@ std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientC
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 @@ void LayerFE::prepareEffectsClientComposition(
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 @@ bool LayerFE::hasBlur() const {
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 b89b6b4b92..b897a90687 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -83,12 +83,13 @@ private:
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/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 3784cf12c5..42a77fd844 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -4323,7 +4323,7 @@ void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) {
std::move(displayInfos),
ftl::to_underlying(vsyncId),
frameTime.ns()},
- std::move(inputWindowCommands.releaseListeners()),
+ inputWindowCommands.releaseListeners(),
/* forceImmediateCall= */ visibleWindowsChanged ||
!inputWindowCommands.getFocusRequests()
.empty());
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index 13f65770cd..c68513e0f0 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -22,6 +22,7 @@ cc_defaults {
],
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 b5f7a74347..37f3aa7e98 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -63,7 +63,10 @@ cc_test {
"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 @@ cc_test {
"libcutils",
"libEGL",
"libGLESv2",
+ "libjnigraphics",
"libgui",
"liblog",
"libnativewindow",
@@ -83,6 +87,7 @@ cc_test {
"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 ad43cdcc5d..b199ddb5f6 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 151611cad2..ada9862f23 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -662,6 +662,93 @@ TEST_P(LayerTypeAndRenderTypeTransactionTest, ParentCornerRadiusPrecedenceClient
}
}
+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 82390ac6f3..1bee27be3f 100644
--- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -504,6 +504,17 @@ public:
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 0000000000..b52d517299
--- /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 0000000000..e1ab54b22a
--- /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 0000000000..bbaf0afa5c
--- /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 0000000000..0fe2ed8a81
--- /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 0000000000..3ee5ac6c43
--- /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 0000000000..e5e8850518
--- /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 07356b9ec9..d045eb884d 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1546,6 +1546,14 @@ TEST_F(LayerSnapshotTest, setShadowRadius) {
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 0bedcd174e..02c3ecd587 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 @@ public:
}
}
+ 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};