Merge "Add multi-display support"
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index f6493bf..02416e4 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -19,6 +19,8 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <cutils/properties.h>
+
 #include <gui/BLASTBufferQueue.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/GLConsumer.h>
@@ -99,7 +101,11 @@
         mHeight(height),
         mNextTransaction(nullptr) {
     BufferQueue::createBufferQueue(&mProducer, &mConsumer);
-    mConsumer->setMaxAcquiredBufferCount(MAX_ACQUIRED_BUFFERS);
+
+    int8_t disableTripleBuffer = property_get_bool("ro.sf.disable_triple_buffer", 0);
+    if (!disableTripleBuffer) {
+        mProducer->setMaxDequeuedBufferCount(2);
+    }
     mBufferItemConsumer =
             new BLASTBufferItemConsumer(mConsumer, AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER, 1, true);
     static int32_t id = 0;
@@ -183,7 +189,7 @@
 
 void BLASTBufferQueue::processNextBufferLocked() {
     ATRACE_CALL();
-    if (mNumFrameAvailable == 0 || mNumAcquired == MAX_ACQUIRED_BUFFERS) {
+    if (mNumFrameAvailable == 0 || mNumAcquired == MAX_ACQUIRED_BUFFERS + 1) {
         return;
     }
 
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index d72eb5a..1b22df2 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -98,7 +98,9 @@
     std::mutex mMutex;
     std::condition_variable mCallbackCV;
 
-    static const int MAX_ACQUIRED_BUFFERS = 2;
+    // BufferQueue internally allows 1 more than
+    // the max to be acquired
+    static const int MAX_ACQUIRED_BUFFERS = 1;
 
     int32_t mNumFrameAvailable GUARDED_BY(mMutex);
     int32_t mNumAcquired GUARDED_BY(mMutex);
diff --git a/libs/renderengine/gl/filters/KawaseBlurFilter.cpp b/libs/renderengine/gl/filters/KawaseBlurFilter.cpp
index fc26bcc..7524c6d 100644
--- a/libs/renderengine/gl/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/gl/filters/KawaseBlurFilter.cpp
@@ -106,21 +106,19 @@
         precision mediump float;
 
         uniform sampler2D uTexture;
-        highp uniform vec2 uOffset;
+        uniform vec2 uOffset;
 
         highp in vec2 vUV;
         out vec4 fragColor;
 
-        vec4 kawaseBlur() {
-            return (texture(uTexture, vec2(-1.0, 1.0) * uOffset + vUV, 0.0)
-                    + texture(uTexture, uOffset + vUV, 0.0)
-                    + texture(uTexture, vec2(1.0, -1.0) * uOffset + vUV, 0.0)
-                    + texture(uTexture, vec2(-1.0) * uOffset + vUV, 0.0))
-                * 0.25;
-        }
-
         void main() {
-            fragColor = kawaseBlur();
+            fragColor  = texture(uTexture, vUV, 0.0);
+            fragColor += texture(uTexture, vUV + vec2( uOffset.x,  uOffset.y), 0.0);
+            fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
+            fragColor += texture(uTexture, vUV + vec2(-uOffset.x,  uOffset.y), 0.0);
+            fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
+
+            fragColor = vec4(fragColor.rgb * 0.2, 1.0);
         }
     )SHADER";
 }
diff --git a/libs/renderengine/gl/filters/KawaseBlurFilter.h b/libs/renderengine/gl/filters/KawaseBlurFilter.h
index ec81f81..20009cf 100644
--- a/libs/renderengine/gl/filters/KawaseBlurFilter.h
+++ b/libs/renderengine/gl/filters/KawaseBlurFilter.h
@@ -30,7 +30,7 @@
 
 class KawaseBlurFilter : public BlurFilter {
 public:
-    static constexpr uint32_t kMaxPasses = 8;
+    static constexpr uint32_t kMaxPasses = 6;
 
     explicit KawaseBlurFilter(GLESRenderEngine& engine);
     status_t prepare() override;
diff --git a/libs/ui/include/ui/DisplayInfo.h b/libs/ui/include/ui/DisplayInfo.h
index 7773319..69f86d3 100644
--- a/libs/ui/include/ui/DisplayInfo.h
+++ b/libs/ui/include/ui/DisplayInfo.h
@@ -20,8 +20,11 @@
 
 namespace android {
 
+enum class DisplayConnectionType { Internal, External };
+
 // Immutable information about physical display.
 struct DisplayInfo {
+    DisplayConnectionType connectionType = DisplayConnectionType::Internal;
     float density = 0.f;
     bool secure = false;
 };
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index f4f45be..d7ec868 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -180,95 +180,91 @@
     }
     bool blackOutLayer = (isProtected() && !targetSettings.supportsProtectedContent) ||
             (isSecure() && !targetSettings.isSecure);
-    const State& s(getDrawingState());
     compositionengine::LayerFE::LayerSettings& layer = *result;
-    if (!blackOutLayer) {
-        layer.source.buffer.buffer = mBufferInfo.mBuffer;
-        layer.source.buffer.isOpaque = isOpaque(s);
-        layer.source.buffer.fence = mBufferInfo.mFence;
-        layer.source.buffer.textureName = mTextureName;
-        layer.source.buffer.usePremultipliedAlpha = getPremultipledAlpha();
-        layer.source.buffer.isY410BT2020 = isHdrY410();
-        bool hasSmpte2086 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086;
-        bool hasCta861_3 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::CTA861_3;
-        layer.source.buffer.maxMasteringLuminance = hasSmpte2086
-                ? mBufferInfo.mHdrMetadata.smpte2086.maxLuminance
-                : defaultMaxMasteringLuminance;
-        layer.source.buffer.maxContentLuminance = hasCta861_3
-                ? mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel
-                : defaultMaxContentLuminance;
-        layer.frameNumber = mCurrentFrameNumber;
-        layer.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0;
-
-        // TODO: we could be more subtle with isFixedSize()
-        const bool useFiltering = targetSettings.needsFiltering || mNeedsFiltering || isFixedSize();
-
-        // Query the texture matrix given our current filtering mode.
-        float textureMatrix[16];
-        getDrawingTransformMatrix(useFiltering, textureMatrix);
-
-        if (getTransformToDisplayInverse()) {
-            /*
-             * the code below applies the primary display's inverse transform to
-             * the texture transform
-             */
-            uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags();
-            mat4 tr = inverseOrientation(transform);
-
-            /**
-             * TODO(b/36727915): This is basically a hack.
-             *
-             * Ensure that regardless of the parent transformation,
-             * this buffer is always transformed from native display
-             * orientation to display orientation. For example, in the case
-             * of a camera where the buffer remains in native orientation,
-             * we want the pixels to always be upright.
-             */
-            sp<Layer> p = mDrawingParent.promote();
-            if (p != nullptr) {
-                const auto parentTransform = p->getTransform();
-                tr = tr * inverseOrientation(parentTransform.getOrientation());
-            }
-
-            // and finally apply it to the original texture matrix
-            const mat4 texTransform(mat4(static_cast<const float*>(textureMatrix)) * tr);
-            memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix));
-        }
-
-        const Rect win{getBounds()};
-        float bufferWidth = getBufferSize(s).getWidth();
-        float bufferHeight = getBufferSize(s).getHeight();
-
-        // BufferStateLayers can have a "buffer size" of [0, 0, -1, -1] when no display frame has
-        // been set and there is no parent layer bounds. In that case, the scale is meaningless so
-        // ignore them.
-        if (!getBufferSize(s).isValid()) {
-            bufferWidth = float(win.right) - float(win.left);
-            bufferHeight = float(win.bottom) - float(win.top);
-        }
-
-        const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight;
-        const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth;
-        const float translateY = float(win.top) / bufferHeight;
-        const float translateX = float(win.left) / bufferWidth;
-
-        // Flip y-coordinates because GLConsumer expects OpenGL convention.
-        mat4 tr = mat4::translate(vec4(.5, .5, 0, 1)) * mat4::scale(vec4(1, -1, 1, 1)) *
-                mat4::translate(vec4(-.5, -.5, 0, 1)) *
-                mat4::translate(vec4(translateX, translateY, 0, 1)) *
-                mat4::scale(vec4(scaleWidth, scaleHeight, 1.0, 1.0));
-
-        layer.source.buffer.useTextureFiltering = useFiltering;
-        layer.source.buffer.textureTransform = mat4(static_cast<const float*>(textureMatrix)) * tr;
-    } else {
-        // If layer is blacked out, force alpha to 1 so that we draw a black color
-        // layer.
-        layer.source.buffer.buffer = nullptr;
-        layer.alpha = 1.0;
-        layer.frameNumber = 0;
-        layer.bufferId = 0;
+    if (blackOutLayer) {
+        prepareClearClientComposition(layer, true /* blackout */);
+        return layer;
     }
 
+    const State& s(getDrawingState());
+    layer.source.buffer.buffer = mBufferInfo.mBuffer;
+    layer.source.buffer.isOpaque = isOpaque(s);
+    layer.source.buffer.fence = mBufferInfo.mFence;
+    layer.source.buffer.textureName = mTextureName;
+    layer.source.buffer.usePremultipliedAlpha = getPremultipledAlpha();
+    layer.source.buffer.isY410BT2020 = isHdrY410();
+    bool hasSmpte2086 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086;
+    bool hasCta861_3 = mBufferInfo.mHdrMetadata.validTypes & HdrMetadata::CTA861_3;
+    layer.source.buffer.maxMasteringLuminance = hasSmpte2086
+            ? mBufferInfo.mHdrMetadata.smpte2086.maxLuminance
+            : defaultMaxMasteringLuminance;
+    layer.source.buffer.maxContentLuminance = hasCta861_3
+            ? mBufferInfo.mHdrMetadata.cta8613.maxContentLightLevel
+            : defaultMaxContentLuminance;
+    layer.frameNumber = mCurrentFrameNumber;
+    layer.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0;
+
+    // TODO: we could be more subtle with isFixedSize()
+    const bool useFiltering = targetSettings.needsFiltering || mNeedsFiltering || isFixedSize();
+
+    // Query the texture matrix given our current filtering mode.
+    float textureMatrix[16];
+    getDrawingTransformMatrix(useFiltering, textureMatrix);
+
+    if (getTransformToDisplayInverse()) {
+        /*
+         * the code below applies the primary display's inverse transform to
+         * the texture transform
+         */
+        uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        mat4 tr = inverseOrientation(transform);
+
+        /**
+         * TODO(b/36727915): This is basically a hack.
+         *
+         * Ensure that regardless of the parent transformation,
+         * this buffer is always transformed from native display
+         * orientation to display orientation. For example, in the case
+         * of a camera where the buffer remains in native orientation,
+         * we want the pixels to always be upright.
+         */
+        sp<Layer> p = mDrawingParent.promote();
+        if (p != nullptr) {
+            const auto parentTransform = p->getTransform();
+            tr = tr * inverseOrientation(parentTransform.getOrientation());
+        }
+
+        // and finally apply it to the original texture matrix
+        const mat4 texTransform(mat4(static_cast<const float*>(textureMatrix)) * tr);
+        memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix));
+    }
+
+    const Rect win{getBounds()};
+    float bufferWidth = getBufferSize(s).getWidth();
+    float bufferHeight = getBufferSize(s).getHeight();
+
+    // BufferStateLayers can have a "buffer size" of [0, 0, -1, -1] when no display frame has
+    // been set and there is no parent layer bounds. In that case, the scale is meaningless so
+    // ignore them.
+    if (!getBufferSize(s).isValid()) {
+        bufferWidth = float(win.right) - float(win.left);
+        bufferHeight = float(win.bottom) - float(win.top);
+    }
+
+    const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight;
+    const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth;
+    const float translateY = float(win.top) / bufferHeight;
+    const float translateX = float(win.left) / bufferWidth;
+
+    // Flip y-coordinates because GLConsumer expects OpenGL convention.
+    mat4 tr = mat4::translate(vec4(.5, .5, 0, 1)) * mat4::scale(vec4(1, -1, 1, 1)) *
+            mat4::translate(vec4(-.5, -.5, 0, 1)) *
+            mat4::translate(vec4(translateX, translateY, 0, 1)) *
+            mat4::scale(vec4(scaleWidth, scaleHeight, 1.0, 1.0));
+
+    layer.source.buffer.useTextureFiltering = useFiltering;
+    layer.source.buffer.textureTransform = mat4(static_cast<const float*>(textureMatrix)) * tr;
+
     return layer;
 }
 
@@ -329,6 +325,7 @@
         Mutex::Autolock lock(mFrameEventHistoryMutex);
         mFrameEventHistory.addPostComposition(mCurrentFrameNumber, glDoneFence, presentFence,
                                               compositorTiming);
+        finalizeFrameEventHistory(glDoneFence, compositorTiming);
     }
 
     // Update mFrameTracker.
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 4085b52..f678910 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -173,14 +173,15 @@
     BufferInfo mBufferInfo;
     virtual void gatherBufferInfo() = 0;
 
+    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
+            compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
+
     /*
      * compositionengine::LayerFE overrides
      */
     const compositionengine::LayerFECompositionState* getCompositionState() const override;
     bool onPreComposition(nsecs_t) override;
     void preparePerFrameCompositionState() override;
-    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
-            compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
 
     // Loads the corresponding system property once per process
     static bool latchUnsignaledBuffers();
@@ -202,12 +203,12 @@
     void updateCloneBufferInfo() override;
     uint64_t mPreviousFrameNumber = 0;
 
+    virtual uint64_t getHeadFrameNumber(nsecs_t expectedPresentTime) const;
+
 private:
     // Returns true if this layer requires filtering
     bool needsFiltering(const sp<const DisplayDevice>& displayDevice) const override;
 
-    uint64_t getHeadFrameNumber(nsecs_t expectedPresentTime) const;
-
     // BufferStateLayers can return Rect::INVALID_RECT if the layer does not have a display frame
     // and its parent layer is not bounded
     Rect getBufferSize(const State& s) const override;
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 1188dfe..de5429b 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -468,6 +468,32 @@
     return mDrawingState.frameNumber;
 }
 
+/**
+ * This is the frameNumber used for deferred transaction signalling. We need to use this because
+ * of cases where we defer a transaction for a surface to itself. In the BLAST world this
+ * may not make a huge amount of sense (Why not just merge the Buffer transaction with the
+ * deferred transaction?) but this is an important legacy use case, for example moving
+ * a window at the same time it draws makes use of this kind of technique. So anyway
+ * imagine we have something like this:
+ *
+ * Transaction { // containing
+ *     Buffer -> frameNumber = 2
+ *     DeferTransactionUntil -> frameNumber = 2
+ *     Random other stuff
+ *  }
+ * Now imagine getHeadFrameNumber returned mDrawingState.mFrameNumber (or mCurrentFrameNumber).
+ * Prior to doTransaction SurfaceFlinger will call notifyAvailableFrames, but because we
+ * haven't swapped mCurrentState to mDrawingState yet we will think the sync point
+ * is not ready. So we will return false from applyPendingState and not swap
+ * current state to drawing state. But because we don't swap current state
+ * to drawing state the number will never update and we will be stuck. This way
+ * we can see we need to return the frame number for the buffer we are about
+ * to apply.
+ */
+uint64_t BufferStateLayer::getHeadFrameNumber(nsecs_t /* expectedPresentTime */) const {
+    return mCurrentState.frameNumber;
+}
+
 bool BufferStateLayer::getAutoRefresh() const {
     // TODO(marissaw): support shared buffer mode
     return false;
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 539442a..753a742 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -116,6 +116,7 @@
 
 protected:
     void gatherBufferInfo() override;
+    uint64_t getHeadFrameNumber(nsecs_t expectedPresentTime) const;
 
 private:
     bool updateFrameEventHistory(const sp<Fence>& acquireFence, nsecs_t postedTime,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index 912dffd..6cc90cb 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -97,6 +97,21 @@
         // Modified by each call to prepareClientComposition to indicate the
         // region of the target buffer that should be cleared.
         Region& clearRegion;
+
+        // Viewport of the target being rendered to. This is used to determine
+        // the shadow light position.
+        const Rect& viewport;
+
+        // Dataspace of the output so we can optimize how to render the shadow
+        // by avoiding unnecessary color space conversions.
+        const ui::Dataspace dataspace;
+
+        // True if the region excluding the shadow is visible.
+        const bool realContentIsVisible;
+
+        // If set to true, change the layer settings to render a clear output.
+        // This may be requested by the HWC
+        const bool clearContent;
     };
 
     // A superset of LayerSettings required by RenderEngine to compose a layer
@@ -109,18 +124,12 @@
         uint64_t frameNumber = 0;
     };
 
-    // Returns the LayerSettings to pass to RenderEngine::drawLayers, or
-    // nullopt_t if the layer does not render
-    virtual std::optional<LayerSettings> prepareClientComposition(
+    // Returns the z-ordered list of LayerSettings to pass to RenderEngine::drawLayers. The list
+    // may contain shadows casted by the layer or the content of the layer itself.  If the layer
+    // does not render then an empty list will be returned.
+    virtual std::vector<LayerSettings> prepareClientCompositionList(
             ClientCompositionTargetSettings&) = 0;
 
-    // Returns the LayerSettings used to draw shadows around a layer. It is passed
-    // to RenderEngine::drawLayers. Returns nullopt_t if the layer does not render
-    // shadows.
-    virtual std::optional<LayerSettings> prepareShadowClientComposition(
-            const LayerSettings& layerSettings, const Rect& displayViewport,
-            ui::Dataspace outputDataspace) = 0;
-
     // Called after the layer is displayed to update the presentation fence
     virtual void onLayerDisplayed(const sp<Fence>&) = 0;
 
@@ -142,7 +151,10 @@
             lhs.useIdentityTransform == rhs.useIdentityTransform &&
             lhs.needsFiltering == rhs.needsFiltering && lhs.isSecure == rhs.isSecure &&
             lhs.supportsProtectedContent == rhs.supportsProtectedContent &&
-            lhs.clearRegion.hasSameRects(rhs.clearRegion);
+            lhs.clearRegion.hasSameRects(rhs.clearRegion) && lhs.viewport == rhs.viewport &&
+            lhs.dataspace == rhs.dataspace &&
+            lhs.realContentIsVisible == rhs.realContentIsVisible &&
+            lhs.clearContent == rhs.clearContent;
 }
 
 static inline bool operator==(const LayerFE::LayerSettings& lhs,
@@ -164,6 +176,12 @@
     *os << "\n    .supportsProtectedContent = " << settings.supportsProtectedContent;
     *os << "\n    .clearRegion = ";
     PrintTo(settings.clearRegion, os);
+    *os << "\n    .viewport = ";
+    PrintTo(settings.viewport, os);
+    *os << "\n    .dataspace = ";
+    PrintTo(settings.dataspace, os);
+    *os << "\n    .realContentIsVisible = " << settings.realContentIsVisible;
+    *os << "\n    .clearContent = " << settings.clearContent;
     *os << "\n}";
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 5c2ad15..45891a7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -35,11 +35,9 @@
     MOCK_METHOD1(onPreComposition, bool(nsecs_t));
 
     MOCK_METHOD1(prepareCompositionState, void(compositionengine::LayerFE::StateSubset));
-    MOCK_METHOD1(prepareClientComposition,
-                 std::optional<LayerSettings>(
+    MOCK_METHOD1(prepareClientCompositionList,
+                 std::vector<compositionengine::LayerFE::LayerSettings>(
                          compositionengine::LayerFE::ClientCompositionTargetSettings&));
-    MOCK_METHOD3(prepareShadowClientComposition,
-                 std::optional<LayerSettings>(const LayerSettings&, const Rect&, ui::Dataspace));
 
     MOCK_METHOD1(onLayerDisplayed, void(const sp<Fence>&));
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index a389bf3..e792f45 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -963,11 +963,16 @@
         // rectangle, as by definition the layer must blend with whatever is
         // underneath. We also skip the first layer as the buffer target is
         // guaranteed to start out cleared.
-        bool clearClientComposition =
+        const bool clearClientComposition =
                 layerState.clearClientTarget && layerFEState->isOpaque && !firstLayer;
 
         ALOGV("  Composition type: client %d clear %d", clientComposition, clearClientComposition);
 
+        // If the layer casts a shadow but the content casting the shadow is occluded, skip
+        // composing the non-shadow content and only draw the shadows.
+        const bool realContentIsVisible = clientComposition &&
+                !layerState.visibleRegion.subtract(layerState.shadowRegion).isEmpty();
+
         if (clientComposition || clearClientComposition) {
             compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{
                     clip,
@@ -976,35 +981,21 @@
                     outputState.isSecure,
                     supportsProtectedContent,
                     clientComposition ? clearRegion : dummyRegion,
+                    outputState.viewport,
+                    outputDataspace,
+                    realContentIsVisible,
+                    !clientComposition, /* clearContent  */
             };
-            if (std::optional<LayerFE::LayerSettings> result =
-                        layerFE.prepareClientComposition(targetSettings)) {
-                if (!clientComposition) {
-                    LayerFE::LayerSettings& layerSettings = *result;
-                    layerSettings.source.buffer.buffer = nullptr;
-                    layerSettings.source.solidColor = half3(0.0, 0.0, 0.0);
-                    layerSettings.alpha = half(0.0);
-                    layerSettings.disableBlending = true;
-                    layerSettings.frameNumber = 0;
-                } else {
-                    std::optional<LayerFE::LayerSettings> shadowLayer =
-                            layerFE.prepareShadowClientComposition(*result, outputState.viewport,
-                                                                   outputDataspace);
-                    if (shadowLayer) {
-                        clientCompositionLayers.push_back(*shadowLayer);
-                    }
-                }
-
-                // If the layer casts a shadow but the content casting the shadow is occluded, skip
-                // composing the non-shadow content and only draw the shadows.
-                const bool skipNonShadowContentComposition = clientComposition &&
-                        layerState.visibleRegion.subtract(layerState.shadowRegion).isEmpty();
-
-                if (!skipNonShadowContentComposition) {
-                    layer->editState().clientCompositionTimestamp = systemTime();
-                    clientCompositionLayers.push_back(*result);
-                }
+            std::vector<LayerFE::LayerSettings> results =
+                    layerFE.prepareClientCompositionList(targetSettings);
+            if (realContentIsVisible && !results.empty()) {
+                layer->editState().clientCompositionTimestamp = systemTime();
             }
+
+            clientCompositionLayers.insert(clientCompositionLayers.end(),
+                                           std::make_move_iterator(results.begin()),
+                                           std::make_move_iterator(results.end()));
+            results.clear();
         }
 
         firstLayer = false;
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 9995ce1..02226ab 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -88,6 +88,7 @@
     MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(DisplayId));
     MOCK_METHOD3(setActiveColorMode, status_t(DisplayId, ui::ColorMode, ui::RenderIntent));
     MOCK_CONST_METHOD0(isUsingVrComposer, bool());
+    MOCK_CONST_METHOD1(getDisplayConnectionType, DisplayConnectionType(DisplayId));
     MOCK_CONST_METHOD1(isVsyncPeriodSwitchSupported, bool(DisplayId));
     MOCK_CONST_METHOD1(getDisplayVsyncPeriod, nsecs_t(DisplayId));
     MOCK_METHOD4(setActiveConfigWithConstraints,
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 2b45046..be0e9e4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3425,19 +3425,13 @@
     LayerFE::LayerSettings mShadowSettings;
     mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(_)).WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[1].mLayerSettings));
-    EXPECT_CALL(mLayers[1].mLayerFE,
-                prepareShadowClientComposition(mLayers[1].mLayerSettings, kDisplayViewport,
-                                               kDisplayDataspace))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mLayerSettings));
-    EXPECT_CALL(mLayers[2].mLayerFE,
-                prepareShadowClientComposition(mLayers[2].mLayerSettings, kDisplayViewport,
-                                               kDisplayDataspace))
-            .WillOnce(Return(mShadowSettings));
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[1].mLayerSettings})));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
+                    {mShadowSettings, mLayers[2].mLayerSettings})));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3469,10 +3463,8 @@
     mLayers[1].mLayerFEState.isOpaque = true;
     mLayers[2].mLayerFEState.isOpaque = true;
 
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mLayerSettings));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3497,10 +3489,8 @@
     mLayers[1].mLayerFEState.isOpaque = false;
     mLayers[2].mLayerFEState.isOpaque = false;
 
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mLayerSettings));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
     Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3529,25 +3519,51 @@
     mLayers[0].mLayerFEState.isOpaque = true;
     mLayers[1].mLayerFEState.isOpaque = true;
     mLayers[2].mLayerFEState.isOpaque = true;
-
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[1].mLayerSettings));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mLayerSettings));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareShadowClientComposition(_, _, _))
-            .WillOnce(Return(std::nullopt));
-
     Region accumClearRegion(Rect(10, 11, 12, 13));
+    Region dummyRegion;
+
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
+            Region(kDisplayFrame),
+            false,       /* identity transform */
+            false,       /* needs filtering */
+            false,       /* secure */
+            false,       /* supports protected content */
+            dummyRegion, /* clear region */
+            kDisplayViewport,
+            kDisplayDataspace,
+            false /* realContentIsVisible */,
+            true /* clearContent */,
+    };
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
+            Region(kDisplayFrame),
+            false, /* identity transform */
+            false, /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
+    };
+
+    LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings;
+    mBlackoutSettings.source.buffer.buffer = nullptr;
+    mBlackoutSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
+    mBlackoutSettings.alpha = 0.f;
+    mBlackoutSettings.disableBlending = true;
+
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mBlackoutSettings})));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
+
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(2u, requests.size());
 
     // The second layer is expected to be rendered as alpha=0 black with no blending
-    EXPECT_EQ(mLayers[1].mLayerSettings.geometry.boundaries, requests[0].geometry.boundaries);
-    EXPECT_FALSE(requests[0].source.buffer.buffer);
-    EXPECT_EQ((half3{0.f, 0.f, 0.f}), requests[0].source.solidColor);
-    EXPECT_EQ(0.f, static_cast<float>(requests[0].alpha));
-    EXPECT_EQ(true, requests[0].disableBlending);
+    EXPECT_EQ(mBlackoutSettings, requests[0]);
 
     EXPECT_EQ(mLayers[2].mLayerSettings, requests[1]);
 
@@ -3569,6 +3585,10 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(Rect(0, 0, 30, 30)),
@@ -3577,6 +3597,10 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(Rect(0, 0, 40, 201)),
@@ -3585,14 +3609,18 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
             mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3613,6 +3641,10 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -3621,6 +3653,10 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3629,14 +3665,18 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
             mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3657,6 +3697,11 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
+
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -3665,6 +3710,10 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3673,14 +3722,18 @@
             false, /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
             mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3700,6 +3753,10 @@
             true,  /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -3708,6 +3765,10 @@
             true,  /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3716,14 +3777,18 @@
             true,  /* secure */
             false, /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
             mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
@@ -3741,6 +3806,10 @@
             false, /* secure */
             true,  /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -3749,6 +3818,10 @@
             false, /* secure */
             true,  /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -3757,14 +3830,18 @@
             false, /* secure */
             true,  /* supports protected content */
             accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
 
-    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
-            .WillOnce(Return(std::nullopt));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(mOutput.generateClientCompositionRequests(true /* supportsProtectedContent */,
                                                                 accumClearRegion,
@@ -3847,16 +3924,16 @@
             true,  /* secure */
             true,  /* supports protected content */
             accumClearRegion,
+            kPortraitViewport,
+            kOutputDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
 
     EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(leftLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(leftLayer.mLayerFE, prepareClientComposition(Eq(ByRef(leftLayerSettings))))
-            .WillOnce(Return(leftLayer.mLayerSettings));
-    EXPECT_CALL(leftLayer.mLayerFE,
-                prepareShadowClientComposition(leftLayer.mLayerSettings, kPortraitViewport,
-                                               kOutputDataspace))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(leftLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(leftLayerSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({leftLayer.mLayerSettings})));
 
     compositionengine::LayerFE::ClientCompositionTargetSettings rightLayerSettings{
             Region(Rect(1000, 0, 2000, 1000)),
@@ -3865,16 +3942,16 @@
             true,  /* secure */
             true,  /* supports protected content */
             accumClearRegion,
+            kPortraitViewport,
+            kOutputDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
     };
 
     EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
     EXPECT_CALL(rightLayer.mOutputLayer, needsFiltering()).WillRepeatedly(Return(false));
-    EXPECT_CALL(rightLayer.mLayerFE, prepareClientComposition(Eq(ByRef(rightLayerSettings))))
-            .WillOnce(Return(rightLayer.mLayerSettings));
-    EXPECT_CALL(rightLayer.mLayerFE,
-                prepareShadowClientComposition(rightLayer.mLayerSettings, kPortraitViewport,
-                                               kOutputDataspace))
-            .WillOnce(Return(std::nullopt));
+    EXPECT_CALL(rightLayer.mLayerFE, prepareClientCompositionList(Eq(ByRef(rightLayerSettings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({rightLayer.mLayerSettings})));
 
     constexpr bool supportsProtectedContent = true;
     auto requests = mOutput.generateClientCompositionRequests(supportsProtectedContent,
@@ -3891,6 +3968,20 @@
     const Region kShadowRegion = Region(kContentWithShadow).subtract(kContent);
     const Region kPartialShadowRegion = Region(kContentWithShadow).subtract(Rect(40, 40, 60, 80));
 
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2Settings{
+            Region(Rect(60, 40, 70, 80)).merge(Rect(40, 80, 70, 90)), /* visible region */
+            false,                                                    /* identity transform */
+            false,                                                    /* needs filtering */
+            false,                                                    /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            false /* realContentIsVisible */,
+            false /* clearContent */,
+    };
+
     LayerFE::LayerSettings mShadowSettings;
     mShadowSettings.source.solidColor = {0.1f, 0.1f, 0.1f};
 
@@ -3899,14 +3990,9 @@
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mLayerSettings));
-    EXPECT_CALL(mLayers[2].mLayerFE,
-                prepareShadowClientComposition(mLayers[2].mLayerSettings, kDisplayViewport,
-                                               kDisplayDataspace))
-            .WillOnce(Return(mShadowSettings));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mShadowSettings})));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(1u, requests.size());
@@ -3928,16 +4014,26 @@
     mLayers[2].mOutputLayerState.visibleRegion = kPartialContentWithPartialShadowRegion;
     mLayers[2].mOutputLayerState.shadowRegion = kShadowRegion;
 
+    Region accumClearRegion(Rect(10, 11, 12, 13));
+    compositionengine::LayerFE::ClientCompositionTargetSettings layer2Settings{
+            Region(Rect(50, 40, 70, 80)).merge(Rect(40, 80, 70, 90)), /* visible region */
+            false,                                                    /* identity transform */
+            false,                                                    /* needs filtering */
+            false,                                                    /* secure */
+            false, /* supports protected content */
+            accumClearRegion,
+            kDisplayViewport,
+            kDisplayDataspace,
+            true /* realContentIsVisible */,
+            false /* clearContent */,
+    };
+
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
-    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientComposition(_))
-            .WillOnce(Return(mLayers[2].mLayerSettings));
-    EXPECT_CALL(mLayers[2].mLayerFE,
-                prepareShadowClientComposition(mLayers[2].mLayerSettings, kDisplayViewport,
-                                               kDisplayDataspace))
-            .WillOnce(Return(mShadowSettings));
+    EXPECT_CALL(mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
+                    {mShadowSettings, mLayers[2].mLayerSettings})));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
     auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
                                                               accumClearRegion, kDisplayDataspace);
     ASSERT_EQ(2u, requests.size());
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 6ff39b4..cd6bbd1 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -49,16 +49,16 @@
 
 DisplayDeviceCreationArgs::DisplayDeviceCreationArgs(const sp<SurfaceFlinger>& flinger,
                                                      const wp<IBinder>& displayToken,
-                                                     const std::optional<DisplayId>& displayId)
+                                                     std::optional<DisplayId> displayId)
       : flinger(flinger), displayToken(displayToken), displayId(displayId) {}
 
 DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs&& args)
       : mFlinger(args.flinger),
         mDisplayToken(args.displayToken),
         mSequenceId(args.sequenceId),
-        mIsVirtual(args.isVirtual),
+        mConnectionType(args.connectionType),
         mCompositionDisplay{mFlinger->getCompositionEngine().createDisplay(
-                compositionengine::DisplayCreationArgs{args.isVirtual, args.displayId,
+                compositionengine::DisplayCreationArgs{args.isVirtual(), args.displayId,
                                                        args.powerAdvisor})},
         mPhysicalOrientation(args.physicalOrientation),
         mIsPrimary(args.isPrimary) {
@@ -248,10 +248,18 @@
 }
 
 std::string DisplayDevice::getDebugName() const {
-    const auto id = getId() ? to_string(*getId()) + ", " : std::string();
-    return base::StringPrintf("DisplayDevice{%s%s%s\"%s\"}", id.c_str(),
-                              isPrimary() ? "primary, " : "", isVirtual() ? "virtual, " : "",
-                              mDisplayName.c_str());
+    std::string displayId;
+    if (const auto id = getId()) {
+        displayId = to_string(*id) + ", ";
+    }
+
+    const char* type = "virtual";
+    if (mConnectionType) {
+        type = *mConnectionType == DisplayConnectionType::Internal ? "internal" : "external";
+    }
+
+    return base::StringPrintf("DisplayDevice{%s%s%s, \"%s\"}", displayId.c_str(), type,
+                              isPrimary() ? ", primary" : "", mDisplayName.c_str());
 }
 
 void DisplayDevice::dump(std::string& result) const {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index f45feae..d970b82 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -28,6 +28,7 @@
 #include <math/mat4.h>
 #include <renderengine/RenderEngine.h>
 #include <system/window.h>
+#include <ui/DisplayInfo.h>
 #include <ui/DisplayState.h>
 #include <ui/GraphicTypes.h>
 #include <ui/HdrCapabilities.h>
@@ -52,7 +53,6 @@
 
 struct CompositionInfo;
 struct DisplayDeviceCreationArgs;
-struct DisplayInfo;
 
 namespace compositionengine {
 class Display;
@@ -71,7 +71,9 @@
         return mCompositionDisplay;
     }
 
-    bool isVirtual() const { return mIsVirtual; }
+    std::optional<DisplayConnectionType> getConnectionType() const { return mConnectionType; }
+
+    bool isVirtual() const { return !mConnectionType; }
     bool isPrimary() const { return mIsPrimary; }
 
     // isSecure indicates whether this display can be trusted to display
@@ -159,7 +161,7 @@
     const sp<SurfaceFlinger> mFlinger;
     const wp<IBinder> mDisplayToken;
     const int32_t mSequenceId;
-    const bool mIsVirtual;
+    const std::optional<DisplayConnectionType> mConnectionType;
 
     const std::shared_ptr<compositionengine::Display> mCompositionDisplay;
 
@@ -178,10 +180,19 @@
 };
 
 struct DisplayDeviceState {
-    bool isVirtual() const { return !displayId.has_value(); }
+    struct Physical {
+        DisplayId id;
+        DisplayConnectionType type;
+
+        bool operator==(const Physical& other) const {
+            return id == other.id && type == other.type;
+        }
+    };
+
+    bool isVirtual() const { return !physical; }
 
     int32_t sequenceId = sNextSequenceId++;
-    std::optional<DisplayId> displayId;
+    std::optional<Physical> physical;
     sp<IGraphicBufferProducer> surface;
     ui::LayerStack layerStack = ui::NO_LAYER_STACK;
     Rect viewport;
@@ -199,15 +210,17 @@
 struct DisplayDeviceCreationArgs {
     // We use a constructor to ensure some of the values are set, without
     // assuming a default value.
-    DisplayDeviceCreationArgs(const sp<SurfaceFlinger>& flinger, const wp<IBinder>& displayToken,
-                              const std::optional<DisplayId>& displayId);
+    DisplayDeviceCreationArgs(const sp<SurfaceFlinger>&, const wp<IBinder>& displayToken,
+                              std::optional<DisplayId>);
+
+    bool isVirtual() const { return !connectionType; }
 
     const sp<SurfaceFlinger> flinger;
     const wp<IBinder> displayToken;
     const std::optional<DisplayId> displayId;
 
     int32_t sequenceId{0};
-    bool isVirtual{false};
+    std::optional<DisplayConnectionType> connectionType;
     bool isSecure{false};
     sp<ANativeWindow> nativeWindow;
     sp<compositionengine::DisplaySurface> displaySurface;
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index e8bf2d8..fc5d441 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -95,13 +95,13 @@
 }
 
 namespace impl {
+
 Display::Display(android::Hwc2::Composer& composer,
                  const std::unordered_set<Capability>& capabilities, hwc2_display_t id,
                  DisplayType type)
       : mComposer(composer),
         mCapabilities(capabilities),
         mId(id),
-        mIsConnected(false),
         mType(type) {
     ALOGV("Created display %" PRIu64, id);
 }
@@ -109,20 +109,27 @@
 Display::~Display() {
     mLayers.clear();
 
-    if (mType == DisplayType::Virtual) {
-        ALOGV("Destroying virtual display");
-        auto intError = mComposer.destroyVirtualDisplay(mId);
-        auto error = static_cast<Error>(intError);
-        ALOGE_IF(error != Error::None, "destroyVirtualDisplay(%" PRIu64
-                ") failed: %s (%d)", mId, to_string(error).c_str(), intError);
-    } else if (mType == DisplayType::Physical) {
-        auto error = setVsyncEnabled(HWC2::Vsync::Disable);
-        if (error != Error::None) {
-            ALOGE("~Display: Failed to disable vsync for display %" PRIu64
-                    ": %s (%d)", mId, to_string(error).c_str(),
-                    static_cast<int32_t>(error));
-        }
+    Error error = Error::None;
+    const char* msg;
+    switch (mType) {
+        case DisplayType::Physical:
+            error = setVsyncEnabled(HWC2::Vsync::Disable);
+            msg = "disable VSYNC for";
+            break;
+
+        case DisplayType::Virtual:
+            error = static_cast<Error>(mComposer.destroyVirtualDisplay(mId));
+            msg = "destroy virtual";
+            break;
+
+        case DisplayType::Invalid: // Used in unit tests.
+            break;
     }
+
+    ALOGE_IF(error != Error::None, "%s: Failed to %s display %" PRIu64 ": %s (%d)", __FUNCTION__,
+             msg, mId, to_string(error).c_str(), static_cast<int32_t>(error));
+
+    ALOGV("Destroyed display %" PRIu64, mId);
 }
 
 // Required by HWC2 display
@@ -372,9 +379,19 @@
     return Error::None;
 }
 
-Error Display::getType(DisplayType* outType) const
-{
-    *outType = mType;
+Error Display::getConnectionType(android::DisplayConnectionType* outType) const {
+    if (mType != DisplayType::Physical) return Error::BadDisplay;
+
+    using ConnectionType = Hwc2::IComposerClient::DisplayConnectionType;
+    ConnectionType connectionType;
+    const auto error = static_cast<Error>(mComposer.getDisplayConnectionType(mId, &connectionType));
+    if (error != Error::None) {
+        return error;
+    }
+
+    *outType = connectionType == ConnectionType::INTERNAL
+            ? android::DisplayConnectionType::Internal
+            : android::DisplayConnectionType::External;
     return Error::None;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 5804903..6549525 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -25,6 +25,7 @@
 
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
+#include <ui/DisplayInfo.h>
 #include <ui/GraphicTypes.h>
 #include <ui/HdrCapabilities.h>
 #include <ui/Region.h>
@@ -191,7 +192,8 @@
     [[clang::warn_unused_result]] virtual Error getRequests(
             DisplayRequest* outDisplayRequests,
             std::unordered_map<Layer*, LayerRequest>* outLayerRequests) = 0;
-    [[clang::warn_unused_result]] virtual Error getType(DisplayType* outType) const = 0;
+    [[clang::warn_unused_result]] virtual Error getConnectionType(
+            android::DisplayConnectionType*) const = 0;
     [[clang::warn_unused_result]] virtual Error supportsDoze(bool* outSupport) const = 0;
     [[clang::warn_unused_result]] virtual Error getHdrCapabilities(
             android::HdrCapabilities* outCapabilities) const = 0;
@@ -268,7 +270,7 @@
     Error getName(std::string* outName) const override;
     Error getRequests(DisplayRequest* outDisplayRequests,
                       std::unordered_map<Layer*, LayerRequest>* outLayerRequests) override;
-    Error getType(DisplayType* outType) const override;
+    Error getConnectionType(android::DisplayConnectionType*) const override;
     Error supportsDoze(bool* outSupport) const override;
     Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override;
     Error getDisplayedContentSamplingAttributes(android::ui::PixelFormat* outFormat,
@@ -332,16 +334,17 @@
     android::Hwc2::Composer& mComposer;
     const std::unordered_set<Capability>& mCapabilities;
 
-    hwc2_display_t mId;
-    bool mIsConnected;
+    const hwc2_display_t mId;
     DisplayType mType;
-    std::unordered_map<hwc2_layer_t, std::unique_ptr<Layer>> mLayers;
+    bool mIsConnected = false;
 
+    std::unordered_map<hwc2_layer_t, std::unique_ptr<Layer>> mLayers;
     std::unordered_map<hwc2_config_t, std::shared_ptr<const Config>> mConfigs;
 
     std::once_flag mDisplayCapabilityQueryFlag;
     std::unordered_set<DisplayCapability> mDisplayCapabilities;
 };
+
 } // namespace impl
 
 class Layer {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index bb6f696..784fa74 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -405,18 +405,32 @@
 
 // Composer 2.4
 
+DisplayConnectionType HWComposer::getDisplayConnectionType(DisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, DisplayConnectionType::Internal);
+    const auto& hwcDisplay = mDisplayData.at(displayId).hwcDisplay;
+
+    DisplayConnectionType type;
+    const auto error = hwcDisplay->getConnectionType(&type);
+
+    const auto FALLBACK_TYPE = hwcDisplay->getId() == mInternalHwcDisplayId
+            ? DisplayConnectionType::Internal
+            : DisplayConnectionType::External;
+
+    RETURN_IF_HWC_ERROR(error, displayId, FALLBACK_TYPE);
+    return type;
+}
+
 bool HWComposer::isVsyncPeriodSwitchSupported(DisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, false);
     return mDisplayData.at(displayId).hwcDisplay->isVsyncPeriodSwitchSupported();
 }
 
 nsecs_t HWComposer::getDisplayVsyncPeriod(DisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, 0);
+
     nsecs_t vsyncPeriodNanos;
     auto error = mDisplayData.at(displayId).hwcDisplay->getDisplayVsyncPeriod(&vsyncPeriodNanos);
-    if (error != HWC2::Error::None) {
-        LOG_DISPLAY_ERROR(displayId, "Failed to get Vsync Period");
-        return 0;
-    }
-
+    RETURN_IF_HWC_ERROR(error, displayId, 0);
     return vsyncPeriodNanos;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 76e831b..41db501 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -189,6 +189,7 @@
     virtual bool isUsingVrComposer() const = 0;
 
     // Composer 2.4
+    virtual DisplayConnectionType getDisplayConnectionType(DisplayId) const = 0;
     virtual bool isVsyncPeriodSwitchSupported(DisplayId displayId) const = 0;
     virtual nsecs_t getDisplayVsyncPeriod(DisplayId displayId) const = 0;
     virtual status_t setActiveConfigWithConstraints(
@@ -326,6 +327,7 @@
     bool isUsingVrComposer() const override;
 
     // Composer 2.4
+    DisplayConnectionType getDisplayConnectionType(DisplayId) const override;
     bool isVsyncPeriodSwitchSupported(DisplayId displayId) const override;
     nsecs_t getDisplayVsyncPeriod(DisplayId displayId) const override;
     status_t setActiveConfigWithConstraints(DisplayId displayId, size_t configId,
diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp
index e928c57..9d45e33 100644
--- a/services/surfaceflinger/EffectLayer.cpp
+++ b/services/surfaceflinger/EffectLayer.cpp
@@ -47,18 +47,35 @@
 
 EffectLayer::~EffectLayer() = default;
 
-std::optional<compositionengine::LayerFE::LayerSettings> EffectLayer::prepareClientComposition(
+std::vector<compositionengine::LayerFE::LayerSettings> EffectLayer::prepareClientCompositionList(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
-    auto result = Layer::prepareClientComposition(targetSettings);
-    if (!result) {
-        return result;
+    std::vector<compositionengine::LayerFE::LayerSettings> results;
+    std::optional<compositionengine::LayerFE::LayerSettings> layerSettings =
+            prepareClientComposition(targetSettings);
+    // Nothing to render.
+    if (!layerSettings) {
+        return {};
     }
-    result->source.solidColor = getColor().rgb;
-    return result;
+
+    std::optional<compositionengine::LayerFE::LayerSettings> shadowSettings =
+            prepareShadowClientComposition(*layerSettings, targetSettings.viewport,
+                                           targetSettings.dataspace);
+    if (shadowSettings) {
+        results.push_back(*shadowSettings);
+    }
+
+    // If fill bounds are occluded or the fill color is invalid skip the fill settings.
+    if (targetSettings.realContentIsVisible && fillsColor()) {
+        // Set color for color fill settings.
+        layerSettings->source.solidColor = getColor().rgb;
+        results.push_back(*layerSettings);
+    }
+
+    return results;
 }
 
 bool EffectLayer::isVisible() const {
-    return !isHiddenByPolicy() && getAlpha() > 0.0_hf;
+    return !isHiddenByPolicy() && getAlpha() > 0.0_hf && hasSomethingToDraw();
 }
 
 bool EffectLayer::setColor(const half3& color) {
@@ -126,6 +143,11 @@
     return layer;
 }
 
+bool EffectLayer::fillsColor() const {
+    return mDrawingState.color.r >= 0.0_hf && mDrawingState.color.g >= 0.0_hf &&
+            mDrawingState.color.b >= 0.0_hf;
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/EffectLayer.h b/services/surfaceflinger/EffectLayer.h
index 8694283..33758b6 100644
--- a/services/surfaceflinger/EffectLayer.h
+++ b/services/surfaceflinger/EffectLayer.h
@@ -52,12 +52,17 @@
      */
     const compositionengine::LayerFECompositionState* getCompositionState() const override;
     void preparePerFrameCompositionState() override;
-    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
-            compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
+    std::vector<compositionengine::LayerFE::LayerSettings> prepareClientCompositionList(
+            compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) override;
 
     std::unique_ptr<compositionengine::LayerFECompositionState> mCompositionState;
 
     sp<Layer> createClone() override;
+
+private:
+    // Returns true if there is a valid color to fill.
+    bool fillsColor() const;
+    bool hasSomethingToDraw() const { return fillsColor() || drawShadows(); }
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 7271f42..da26a37 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -511,13 +511,12 @@
     compositionState->hasProtectedContent = isProtected();
 
     const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f;
-    const bool drawsShadows = mEffectiveShadowRadius != 0.f;
 
     compositionState->isOpaque =
             isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf;
 
     // Force client composition for special cases known only to the front-end.
-    if (isHdrY410() || usesRoundedCorners || drawsShadows) {
+    if (isHdrY410() || usesRoundedCorners || drawShadows()) {
         compositionState->forceClientComposition = true;
     }
 }
@@ -672,6 +671,49 @@
     return shadowLayer;
 }
 
+void Layer::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings,
+                                          bool blackout) const {
+    layerSettings.source.buffer.buffer = nullptr;
+    layerSettings.source.solidColor = half3(0.0, 0.0, 0.0);
+    layerSettings.disableBlending = true;
+    layerSettings.frameNumber = 0;
+
+    // If layer is blacked out, force alpha to 1 so that we draw a black color layer.
+    layerSettings.alpha = blackout ? 1.0f : 0.0f;
+}
+
+std::vector<compositionengine::LayerFE::LayerSettings> Layer::prepareClientCompositionList(
+        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
+    std::optional<compositionengine::LayerFE::LayerSettings> layerSettings =
+            prepareClientComposition(targetSettings);
+    // Nothing to render.
+    if (!layerSettings) {
+        return {};
+    }
+
+    // HWC requests to clear this layer.
+    if (targetSettings.clearContent) {
+        prepareClearClientComposition(*layerSettings, false /* blackout */);
+        return {*layerSettings};
+    }
+
+    std::optional<compositionengine::LayerFE::LayerSettings> shadowSettings =
+            prepareShadowClientComposition(*layerSettings, targetSettings.viewport,
+                                           targetSettings.dataspace);
+    // There are no shadows to render.
+    if (!shadowSettings) {
+        return {*layerSettings};
+    }
+
+    // If the layer casts a shadow but the content casting the shadow is occluded, skip
+    // composing the non-shadow content and only draw the shadows.
+    if (targetSettings.realContentIsVisible) {
+        return {*shadowSettings, *layerSettings};
+    }
+
+    return {*shadowSettings};
+}
+
 Hwc2::IComposerClient::Composition Layer::getCompositionType(
         const sp<const DisplayDevice>& display) const {
     const auto outputLayer = findOutputLayerForDisplay(display);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c2dbd14..37ae340 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -552,6 +552,14 @@
     void updateClonedRelatives(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap);
     void addChildToDrawing(const sp<Layer>& layer);
     void updateClonedInputInfo(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap);
+    virtual std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
+            compositionengine::LayerFE::ClientCompositionTargetSettings&);
+    virtual std::optional<compositionengine::LayerFE::LayerSettings> prepareShadowClientComposition(
+            const LayerFE::LayerSettings& layerSettings, const Rect& displayViewport,
+            ui::Dataspace outputDataspace);
+    // Modifies the passed in layer settings to clear the contents. If the blackout flag is set,
+    // the settings clears the content with a solid black fill.
+    void prepareClearClientComposition(LayerFE::LayerSettings& layerSettings, bool blackout) const;
 
 public:
     /*
@@ -560,11 +568,8 @@
     const compositionengine::LayerFECompositionState* getCompositionState() const override;
     bool onPreComposition(nsecs_t) override;
     void prepareCompositionState(compositionengine::LayerFE::StateSubset subset) override;
-    std::optional<LayerSettings> prepareClientComposition(
+    std::vector<compositionengine::LayerFE::LayerSettings> prepareClientCompositionList(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
-    std::optional<LayerSettings> prepareShadowClientComposition(
-            const LayerFE::LayerSettings& layerSettings, const Rect& displayViewport,
-            ui::Dataspace outputDataspace) override;
     void onLayerDisplayed(const sp<Fence>& releaseFence) override;
     const char* getDebugName() const override;
 
@@ -704,6 +709,7 @@
     half getAlpha() const;
     half4 getColor() const;
     int32_t getBackgroundBlurRadius() const;
+    bool drawShadows() const { return mEffectiveShadowRadius > 0.f; };
 
     // Returns how rounded corners should be drawn for this layer.
     // This will traverse the hierarchy until it reaches its root, finding topmost rounded
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 46fd893..a98ff4f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -783,14 +783,18 @@
         return NAME_NOT_FOUND;
     }
 
-    if (display->isVirtual()) {
+    if (const auto connectionType = display->getConnectionType())
+        info->connectionType = *connectionType;
+    else {
         return INVALID_OPERATION;
     }
 
     if (mEmulatedDisplayDensity) {
         info->density = mEmulatedDisplayDensity;
     } else {
-        info->density = display->isPrimary() ? mInternalDisplayDensity : FALLBACK_DENSITY;
+        info->density = info->connectionType == DisplayConnectionType::Internal
+                ? mInternalDisplayDensity
+                : FALLBACK_DENSITY;
     }
 
     info->secure = display->isSecure();
@@ -2242,30 +2246,38 @@
             continue;
         }
 
+        const DisplayId displayId = info->id;
+        const auto it = mPhysicalDisplayTokens.find(displayId);
+
         if (event.connection == HWC2::Connection::Connected) {
-            if (!mPhysicalDisplayTokens.count(info->id)) {
-                ALOGV("Creating display %s", to_string(info->id).c_str());
+            if (it == mPhysicalDisplayTokens.end()) {
+                ALOGV("Creating display %s", to_string(displayId).c_str());
+
                 if (event.hwcDisplayId == getHwComposer().getInternalHwcDisplayId()) {
-                    initScheduler(info->id);
+                    initScheduler(displayId);
                 }
-                mPhysicalDisplayTokens[info->id] = new BBinder();
+
                 DisplayDeviceState state;
-                state.displayId = info->id;
+                state.physical = {displayId, getHwComposer().getDisplayConnectionType(displayId)};
                 state.isSecure = true; // All physical displays are currently considered secure.
                 state.displayName = info->name;
-                mCurrentState.displays.add(mPhysicalDisplayTokens[info->id], state);
+
+                sp<IBinder> token = new BBinder();
+                mCurrentState.displays.add(token, state);
+                mPhysicalDisplayTokens.emplace(displayId, std::move(token));
+
                 mInterceptor->saveDisplayCreation(state);
             }
         } else {
-            ALOGV("Removing display %s", to_string(info->id).c_str());
+            ALOGV("Removing display %s", to_string(displayId).c_str());
 
-            ssize_t index = mCurrentState.displays.indexOfKey(mPhysicalDisplayTokens[info->id]);
+            const ssize_t index = mCurrentState.displays.indexOfKey(it->second);
             if (index >= 0) {
                 const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
                 mInterceptor->saveDisplayDeletion(state.sequenceId);
                 mCurrentState.displays.removeItemsAt(index);
             }
-            mPhysicalDisplayTokens.erase(info->id);
+            mPhysicalDisplayTokens.erase(it);
         }
 
         processDisplayChangesLocked();
@@ -2285,13 +2297,16 @@
         const sp<IGraphicBufferProducer>& producer) {
     DisplayDeviceCreationArgs creationArgs(this, displayToken, displayId);
     creationArgs.sequenceId = state.sequenceId;
-    creationArgs.isVirtual = state.isVirtual();
     creationArgs.isSecure = state.isSecure;
     creationArgs.displaySurface = dispSurface;
     creationArgs.hasWideColorGamut = false;
     creationArgs.supportedPerFrameMetadata = 0;
     creationArgs.powerAdvisor = displayId ? &mPowerAdvisor : nullptr;
 
+    if (const auto& physical = state.physical) {
+        creationArgs.connectionType = physical->type;
+    }
+
     const bool isInternalDisplay = displayId && displayId == getInternalDisplayIdLocked();
     creationArgs.isPrimary = isInternalDisplay;
 
@@ -2482,8 +2497,8 @@
                              "surface is provided (%p), ignoring it",
                              state.surface.get());
 
-                    displayId = state.displayId;
-                    LOG_ALWAYS_FATAL_IF(!displayId);
+                    LOG_FATAL_IF(!state.physical);
+                    displayId = state.physical->id;
                     dispSurface = new FramebufferSurface(getHwComposer(), *displayId, bqConsumer);
                     producer = bqProducer;
                 }
@@ -5552,17 +5567,18 @@
                 renderArea.isSecure(),
                 supportProtectedContent,
                 clearRegion,
+                displayViewport,
+                clientCompositionDisplay.outputDataspace,
+                true,  /* realContentIsVisible */
+                false, /* clearContent */
         };
-        auto result = layer->prepareClientComposition(targetSettings);
-        if (result) {
-            std::optional<compositionengine::LayerFE::LayerSettings> shadowLayer =
-                    layer->prepareShadowClientComposition(*result, displayViewport,
-                                                          clientCompositionDisplay.outputDataspace);
-            if (shadowLayer) {
-                clientCompositionLayers.push_back(*shadowLayer);
-            }
-            clientCompositionLayers.push_back(*result);
-        }
+        std::vector<compositionengine::LayerFE::LayerSettings> results =
+                layer->prepareClientCompositionList(targetSettings);
+        clientCompositionLayers.insert(clientCompositionLayers.end(),
+                                       std::make_move_iterator(results.begin()),
+                                       std::make_move_iterator(results.end()));
+        results.clear();
+
     });
 
     std::vector<const renderengine::LayerSettings*> clientCompositionLayerPointers;
diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp
index 6884b4c..1f9d46c 100644
--- a/services/surfaceflinger/SurfaceInterceptor.cpp
+++ b/services/surfaceflinger/SurfaceInterceptor.cpp
@@ -595,8 +595,8 @@
     creation->set_id(info.sequenceId);
     creation->set_name(info.displayName);
     creation->set_is_secure(info.isSecure);
-    if (info.displayId) {
-        creation->set_display_id(info.displayId->value);
+    if (info.physical) {
+        creation->set_display_id(info.physical->id.value);
     }
 }
 
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 7c8c28e..8038eba 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -55,28 +55,6 @@
     return result;
 }
 
-AStatsManager_PullAtomCallbackReturn TimeStats::populateGlobalAtom(AStatsEventList* data) {
-    std::lock_guard<std::mutex> lock(mMutex);
-
-    if (mTimeStats.statsStart == 0) {
-        return AStatsManager_PULL_SKIP;
-    }
-    flushPowerTimeLocked();
-
-    AStatsEvent* event = mStatsDelegate->addStatsEventToPullData(data);
-    mStatsDelegate->statsEventSetAtomId(event, android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.totalFrames);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.missedFrames);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.clientCompositionFrames);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.displayOnTime);
-    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.presentToPresent.totalTime());
-    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.displayEventConnectionsCount);
-    mStatsDelegate->statsEventBuild(event);
-    clearGlobalLocked();
-
-    return AStatsManager_PULL_SUCCESS;
-}
-
 namespace {
 // Histograms align with the order of fields in SurfaceflingerStatsLayerInfo.
 const std::array<std::string, 6> kHistogramNames = {
@@ -112,6 +90,37 @@
 }
 } // namespace
 
+AStatsManager_PullAtomCallbackReturn TimeStats::populateGlobalAtom(AStatsEventList* data) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    if (mTimeStats.statsStart == 0) {
+        return AStatsManager_PULL_SKIP;
+    }
+    flushPowerTimeLocked();
+
+    AStatsEvent* event = mStatsDelegate->addStatsEventToPullData(data);
+    mStatsDelegate->statsEventSetAtomId(event, android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
+    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.totalFrames);
+    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.missedFrames);
+    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.clientCompositionFrames);
+    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.displayOnTime);
+    mStatsDelegate->statsEventWriteInt64(event, mTimeStats.presentToPresent.totalTime());
+    mStatsDelegate->statsEventWriteInt32(event, mTimeStats.displayEventConnectionsCount);
+    std::string frameDurationBytes =
+            histogramToProtoByteString(mTimeStats.frameDuration.hist, mMaxPulledHistogramBuckets);
+    mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)frameDurationBytes.c_str(),
+                                             frameDurationBytes.size());
+    std::string renderEngineTimingBytes =
+            histogramToProtoByteString(mTimeStats.renderEngineTiming.hist,
+                                       mMaxPulledHistogramBuckets);
+    mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)renderEngineTimingBytes.c_str(),
+                                             renderEngineTimingBytes.size());
+    mStatsDelegate->statsEventBuild(event);
+    clearGlobalLocked();
+
+    return AStatsManager_PULL_SUCCESS;
+}
+
 AStatsManager_PullAtomCallbackReturn TimeStats::populateLayerAtom(AStatsEventList* data) {
     std::lock_guard<std::mutex> lock(mMutex);
 
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index 6c8eb27..83e5060 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -747,12 +747,26 @@
                                                 ISurfaceComposerClient::eFXSurfaceEffect));
     Transaction()
             .setCrop_legacy(colorLayer, Rect(0, 0, 32, 32))
-            .setColor(colorLayer, half3(2.0f, -1.0f, 0.0f))
+            .setColor(colorLayer, half3(2.0f, 0.0f, 0.0f))
             .apply();
 
     getScreenCapture()->expectColor(Rect(0, 0, 32, 32), Color::RED);
 }
 
+// An invalid color will not render a color and the layer will not be visible.
+TEST_P(LayerRenderTypeTransactionTest, SetInvalidColor) {
+    sp<SurfaceControl> colorLayer;
+    ASSERT_NO_FATAL_FAILURE(colorLayer =
+                                    createLayer("test", 0 /* buffer width */, 0 /* buffer height */,
+                                                ISurfaceComposerClient::eFXSurfaceEffect));
+    Transaction()
+            .setCrop_legacy(colorLayer, Rect(0, 0, 32, 32))
+            .setColor(colorLayer, half3(1.0f, -1.0f, 0.5f))
+            .apply();
+
+    getScreenCapture()->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+}
+
 TEST_P(LayerRenderTypeTransactionTest, SetColorWithAlpha) {
     sp<SurfaceControl> bufferLayer;
     sp<SurfaceControl> colorLayer;
diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
index 0e7eba8..8d97f27 100644
--- a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
+++ b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
@@ -145,6 +145,7 @@
         mBGSurfaceControl.clear();
         mFGSurfaceControl.clear();
         mComposerClient.clear();
+        system("setenforce 1");
     }
 
     sp<SurfaceComposerClient> mComposerClient;
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp
index 2f89696..96a7541 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp
@@ -183,6 +183,7 @@
     // Wait for mock call signaling teardown?
     property_set("debug.sf.nobootanimation", "0");
     property_set("debug.sf.hwc_service_name", "default");
+    system("setenforce 1");
     ALOGI("Test env tear down - done");
 }
 
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 1dfb6ba..06ef8e7 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -284,13 +284,14 @@
         EXPECT_CALL(*test->mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT)).Times(1);
         EXPECT_CALL(*test->mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT)).Times(1);
         EXPECT_CALL(*test->mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64)).Times(1);
-        test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, DEFAULT_DISPLAY_ID,
-                                                   false /* isVirtual */, true /* isPrimary */)
-                                 .setDisplaySurface(test->mDisplaySurface)
-                                 .setNativeWindow(test->mNativeWindow)
-                                 .setSecure(Derived::IS_SECURE)
-                                 .setPowerMode(Derived::INIT_POWER_MODE)
-                                 .inject();
+        test->mDisplay =
+                FakeDisplayDeviceInjector(test->mFlinger, DEFAULT_DISPLAY_ID,
+                                          DisplayConnectionType::Internal, true /* isPrimary */)
+                        .setDisplaySurface(test->mDisplaySurface)
+                        .setNativeWindow(test->mNativeWindow)
+                        .setSecure(Derived::IS_SECURE)
+                        .setPowerMode(Derived::INIT_POWER_MODE)
+                        .inject();
         Mock::VerifyAndClear(test->mNativeWindow);
         test->mDisplay->setLayerStack(DEFAULT_LAYER_STACK);
     }
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 232255f..4da0647 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -310,6 +310,16 @@
     static std::optional<DisplayId> get() { return {}; }
 };
 
+template <typename>
+struct DisplayConnectionTypeGetter {
+    static constexpr std::optional<DisplayConnectionType> value;
+};
+
+template <typename PhysicalDisplay>
+struct DisplayConnectionTypeGetter<PhysicalDisplayId<PhysicalDisplay>> {
+    static constexpr std::optional<DisplayConnectionType> value = PhysicalDisplay::CONNECTION_TYPE;
+};
+
 // DisplayIdType can be:
 //     1) PhysicalDisplayId<...> for generated ID of physical display backed by HWC.
 //     2) VirtualDisplayId<...> for hard-coded ID of virtual display backed by HWC.
@@ -318,6 +328,7 @@
           Secure secure, Primary primary, int grallocUsage>
 struct DisplayVariant {
     using DISPLAY_ID = DisplayIdGetter<DisplayIdType>;
+    using CONNECTION_TYPE = DisplayConnectionTypeGetter<DisplayIdType>;
 
     // The display width and height
     static constexpr int WIDTH = width;
@@ -343,8 +354,8 @@
 
     static auto makeFakeExistingDisplayInjector(DisplayTransactionTest* test) {
         auto injector =
-                FakeDisplayDeviceInjector(test->mFlinger, DISPLAY_ID::get(),
-                                          static_cast<bool>(VIRTUAL), static_cast<bool>(PRIMARY));
+                FakeDisplayDeviceInjector(test->mFlinger, DISPLAY_ID::get(), CONNECTION_TYPE::value,
+                                          static_cast<bool>(PRIMARY));
 
         injector.setSecure(static_cast<bool>(SECURE));
         injector.setNativeWindow(test->mNativeWindow);
@@ -448,6 +459,15 @@
     }
 
     static void setupHwcHotplugCallExpectations(DisplayTransactionTest* test) {
+        constexpr auto CONNECTION_TYPE =
+                PhysicalDisplay::CONNECTION_TYPE == DisplayConnectionType::Internal
+                ? IComposerClient::DisplayConnectionType::INTERNAL
+                : IComposerClient::DisplayConnectionType::EXTERNAL;
+
+        EXPECT_CALL(*test->mComposer, getDisplayConnectionType(HWC_DISPLAY_ID, _))
+                .WillOnce(
+                        DoAll(SetArgPointee<1>(CONNECTION_TYPE), Return(Hwc2::V2_4::Error::NONE)));
+
         EXPECT_CALL(*test->mComposer, setClientTargetSlotCount(_)).WillOnce(Return(Error::NONE));
         EXPECT_CALL(*test->mComposer, getDisplayConfigs(HWC_DISPLAY_ID, _))
                 .WillOnce(DoAll(SetArgPointee<1>(std::vector<unsigned>{HWC_ACTIVE_CONFIG_ID}),
@@ -512,6 +532,7 @@
 
 template <bool hasIdentificationData>
 struct PrimaryDisplay {
+    static constexpr auto CONNECTION_TYPE = DisplayConnectionType::Internal;
     static constexpr Primary PRIMARY = Primary::TRUE;
     static constexpr uint8_t PORT = 255;
     static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
@@ -520,6 +541,7 @@
 
 template <bool hasIdentificationData>
 struct ExternalDisplay {
+    static constexpr auto CONNECTION_TYPE = DisplayConnectionType::External;
     static constexpr Primary PRIMARY = Primary::FALSE;
     static constexpr uint8_t PORT = 254;
     static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
@@ -1181,7 +1203,8 @@
 
     GetBestColorModeTest()
           : DisplayTransactionTest(),
-            mInjector(FakeDisplayDeviceInjector(mFlinger, DEFAULT_DISPLAY_ID, false /* isVirtual */,
+            mInjector(FakeDisplayDeviceInjector(mFlinger, DEFAULT_DISPLAY_ID,
+                                                DisplayConnectionType::Internal,
                                                 true /* isPrimary */)) {}
 
     void setHasWideColorGamut(bool hasWideColorGamut) { mHasWideColorGamut = hasWideColorGamut; }
@@ -1283,8 +1306,6 @@
 class DisplayDeviceSetProjectionTest : public DisplayTransactionTest {
 public:
     static constexpr DisplayId DEFAULT_DISPLAY_ID = DisplayId{777};
-    static constexpr bool DEFAULT_DISPLAY_IS_VIRTUAL = false;
-    static constexpr bool DEFAULT_DISPLAY_IS_PRIMARY = true;
     static constexpr int32_t DEFAULT_DISPLAY_WIDTH = 1080;  // arbitrary
     static constexpr int32_t DEFAULT_DISPLAY_HEIGHT = 1920; // arbitrary
 
@@ -1314,8 +1335,8 @@
         EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
         EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT));
 
-        return FakeDisplayDeviceInjector(mFlinger, DEFAULT_DISPLAY_ID, DEFAULT_DISPLAY_IS_VIRTUAL,
-                                         DEFAULT_DISPLAY_IS_PRIMARY)
+        return FakeDisplayDeviceInjector(mFlinger, DEFAULT_DISPLAY_ID,
+                                         DisplayConnectionType::Internal, true /* isPrimary */)
                 .setNativeWindow(mNativeWindow)
                 .setPhysicalOrientation(mPhysicalOrientation)
                 .inject();
@@ -1645,8 +1666,12 @@
     // Invocation
 
     DisplayDeviceState state;
-    state.displayId = static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
-                                                                : Case::Display::DISPLAY_ID::get();
+    if (const auto connectionType = Case::Display::CONNECTION_TYPE::value) {
+        const auto displayId = Case::Display::DISPLAY_ID::get();
+        ASSERT_TRUE(displayId);
+        state.physical = {*displayId, *connectionType};
+    }
+
     state.isSecure = static_cast<bool>(Case::Display::SECURE);
 
     auto device =
@@ -1658,6 +1683,7 @@
 
     ASSERT_TRUE(device != nullptr);
     EXPECT_EQ(Case::Display::DISPLAY_ID::get(), device->getId());
+    EXPECT_EQ(Case::Display::CONNECTION_TYPE::value, device->getConnectionType());
     EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), device->isVirtual());
     EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
     EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
@@ -1821,21 +1847,24 @@
     EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
     EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
 
+    std::optional<DisplayDeviceState::Physical> expectedPhysical;
+    if (const auto connectionType = Case::Display::CONNECTION_TYPE::value) {
+        const auto displayId = Case::Display::DISPLAY_ID::get();
+        ASSERT_TRUE(displayId);
+        expectedPhysical = {*displayId, *connectionType};
+    }
+
     // The display should have been set up in the current display state
     ASSERT_TRUE(hasCurrentDisplayState(displayToken));
     const auto& current = getCurrentDisplayState(displayToken);
     EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), current.isVirtual());
-    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
-                                                        : Case::Display::DISPLAY_ID::get(),
-              current.displayId);
+    EXPECT_EQ(expectedPhysical, current.physical);
 
     // The display should have been set up in the drawing display state
     ASSERT_TRUE(hasDrawingDisplayState(displayToken));
     const auto& draw = getDrawingDisplayState(displayToken);
     EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), draw.isVirtual());
-    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
-                                                        : Case::Display::DISPLAY_ID::get(),
-              draw.displayId);
+    EXPECT_EQ(expectedPhysical, draw.physical);
 }
 
 template <typename Case>
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 685cfaf..3a4f349 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -543,10 +543,11 @@
     class FakeDisplayDeviceInjector {
     public:
         FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger,
-                                  const std::optional<DisplayId>& displayId, bool isVirtual,
+                                  std::optional<DisplayId> displayId,
+                                  std::optional<DisplayConnectionType> connectionType,
                                   bool isPrimary)
               : mFlinger(flinger), mCreationArgs(flinger.mFlinger.get(), mDisplayToken, displayId) {
-            mCreationArgs.isVirtual = isVirtual;
+            mCreationArgs.connectionType = connectionType;
             mCreationArgs.isPrimary = isPrimary;
         }
 
@@ -609,7 +610,12 @@
 
         sp<DisplayDevice> inject() {
             DisplayDeviceState state;
-            state.displayId = mCreationArgs.isVirtual ? std::nullopt : mCreationArgs.displayId;
+            if (const auto type = mCreationArgs.connectionType) {
+                const auto id = mCreationArgs.displayId;
+                LOG_ALWAYS_FATAL_IF(!id);
+                state.physical = {*id, *type};
+            }
+
             state.isSecure = mCreationArgs.isSecure;
 
             sp<DisplayDevice> device = new DisplayDevice(std::move(mCreationArgs));
@@ -617,9 +623,8 @@
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
-            if (!mCreationArgs.isVirtual) {
-                LOG_ALWAYS_FATAL_IF(!state.displayId);
-                mFlinger.mutablePhysicalDisplayTokens()[*state.displayId] = mDisplayToken;
+            if (const auto& physical = state.physical) {
+                mFlinger.mutablePhysicalDisplayTokens()[physical->id] = mDisplayToken;
             }
 
             return device;
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 685dfba..a7a4d48 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -797,63 +797,6 @@
     ASSERT_EQ(0, globalProto.stats_size());
 }
 
-TEST_F(TimeStatsTest, globalStatsCallback) {
-    constexpr size_t TOTAL_FRAMES = 5;
-    constexpr size_t MISSED_FRAMES = 4;
-    constexpr size_t CLIENT_COMPOSITION_FRAMES = 3;
-    constexpr size_t DISPLAY_EVENT_CONNECTIONS = 14;
-
-    mTimeStats->onBootFinished();
-    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
-
-    for (size_t i = 0; i < TOTAL_FRAMES; i++) {
-        mTimeStats->incrementTotalFrames();
-    }
-    for (size_t i = 0; i < MISSED_FRAMES; i++) {
-        mTimeStats->incrementMissedFrames();
-    }
-    for (size_t i = 0; i < CLIENT_COMPOSITION_FRAMES; i++) {
-        mTimeStats->incrementClientCompositionFrames();
-    }
-
-    mTimeStats->recordDisplayEventConnectionCount(DISPLAY_EVENT_CONNECTIONS);
-
-    mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL);
-    mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000));
-    mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
-
-    EXPECT_THAT(mDelegate->mAtomTags,
-                UnorderedElementsAre(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
-                                     android::util::SURFACEFLINGER_STATS_LAYER_INFO));
-    EXPECT_NE(nullptr, mDelegate->mCallback);
-    EXPECT_EQ(mTimeStats.get(), mDelegate->mCookie);
-
-    {
-        InSequence seq;
-        EXPECT_CALL(*mDelegate,
-                    statsEventSetAtomId(mDelegate->mEvent,
-                                        android::util::SURFACEFLINGER_STATS_GLOBAL_INFO));
-        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, TOTAL_FRAMES));
-        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, MISSED_FRAMES));
-        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, CLIENT_COMPOSITION_FRAMES));
-        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, _));
-        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, 2));
-        EXPECT_CALL(*mDelegate, statsEventWriteInt32(mDelegate->mEvent, DISPLAY_EVENT_CONNECTIONS));
-        EXPECT_CALL(*mDelegate, statsEventBuild(mDelegate->mEvent));
-    }
-    EXPECT_EQ(AStatsManager_PULL_SUCCESS,
-              mDelegate->makePullAtomCallback(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
-                                              mDelegate->mCookie));
-
-    SFTimeStatsGlobalProto globalProto;
-    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
-
-    EXPECT_EQ(0, globalProto.total_frames());
-    EXPECT_EQ(0, globalProto.missed_frames());
-    EXPECT_EQ(0, globalProto.client_composition_frames());
-    EXPECT_EQ(0, globalProto.present_to_present_size());
-}
-
 namespace {
 std::string buildExpectedHistogramBytestring(const std::vector<int32_t>& times,
                                              const std::vector<int32_t>& frameCounts) {
@@ -897,6 +840,80 @@
     return expected == actual;
 }
 
+TEST_F(TimeStatsTest, globalStatsCallback) {
+    constexpr size_t TOTAL_FRAMES = 5;
+    constexpr size_t MISSED_FRAMES = 4;
+    constexpr size_t CLIENT_COMPOSITION_FRAMES = 3;
+    constexpr size_t DISPLAY_EVENT_CONNECTIONS = 14;
+
+    mTimeStats->onBootFinished();
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    for (size_t i = 0; i < TOTAL_FRAMES; i++) {
+        mTimeStats->incrementTotalFrames();
+    }
+    for (size_t i = 0; i < MISSED_FRAMES; i++) {
+        mTimeStats->incrementMissedFrames();
+    }
+    for (size_t i = 0; i < CLIENT_COMPOSITION_FRAMES; i++) {
+        mTimeStats->incrementClientCompositionFrames();
+    }
+
+    mTimeStats->recordDisplayEventConnectionCount(DISPLAY_EVENT_CONNECTIONS);
+    mTimeStats->setPowerMode(HWC_POWER_MODE_NORMAL);
+    mTimeStats->recordFrameDuration(1000000, 3000000);
+    mTimeStats->recordRenderEngineDuration(2000000, 4000000);
+    mTimeStats->recordRenderEngineDuration(2000000, std::make_shared<FenceTime>(3000000));
+
+    mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000));
+    mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
+
+    EXPECT_THAT(mDelegate->mAtomTags,
+                UnorderedElementsAre(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
+                                     android::util::SURFACEFLINGER_STATS_LAYER_INFO));
+    EXPECT_NE(nullptr, mDelegate->mCallback);
+    EXPECT_EQ(mTimeStats.get(), mDelegate->mCookie);
+
+    std::string expectedFrameDuration = buildExpectedHistogramBytestring({2}, {1});
+    std::string expectedRenderEngineTiming = buildExpectedHistogramBytestring({1, 2}, {1, 1});
+
+    {
+        InSequence seq;
+        EXPECT_CALL(*mDelegate,
+                    statsEventSetAtomId(mDelegate->mEvent,
+                                        android::util::SURFACEFLINGER_STATS_GLOBAL_INFO));
+        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, TOTAL_FRAMES));
+        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, MISSED_FRAMES));
+        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, CLIENT_COMPOSITION_FRAMES));
+        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, _));
+        EXPECT_CALL(*mDelegate, statsEventWriteInt64(mDelegate->mEvent, 2));
+        EXPECT_CALL(*mDelegate, statsEventWriteInt32(mDelegate->mEvent, DISPLAY_EVENT_CONNECTIONS));
+        EXPECT_CALL(*mDelegate,
+                    statsEventWriteByteArray(mDelegate->mEvent,
+                                             BytesEq((const uint8_t*)expectedFrameDuration.c_str(),
+                                                     expectedFrameDuration.size()),
+                                             expectedFrameDuration.size()));
+        EXPECT_CALL(*mDelegate,
+                    statsEventWriteByteArray(mDelegate->mEvent,
+                                             BytesEq((const uint8_t*)
+                                                             expectedRenderEngineTiming.c_str(),
+                                                     expectedRenderEngineTiming.size()),
+                                             expectedRenderEngineTiming.size()));
+        EXPECT_CALL(*mDelegate, statsEventBuild(mDelegate->mEvent));
+    }
+    EXPECT_EQ(AStatsManager_PULL_SUCCESS,
+              mDelegate->makePullAtomCallback(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
+                                              mDelegate->mCookie));
+
+    SFTimeStatsGlobalProto globalProto;
+    ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+    EXPECT_EQ(0, globalProto.total_frames());
+    EXPECT_EQ(0, globalProto.missed_frames());
+    EXPECT_EQ(0, globalProto.client_composition_frames());
+    EXPECT_EQ(0, globalProto.present_to_present_size());
+}
+
 TEST_F(TimeStatsTest, layerStatsCallback_pullsAllAndClears) {
     constexpr size_t LATE_ACQUIRE_FRAMES = 2;
     constexpr size_t BAD_DESIRED_PRESENT_FRAMES = 3;