Merge "Allow single inactive layers to be cached if they would be used for a hole punch."
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 8ec15ed..7c2700c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -98,8 +98,8 @@
                              std::chrono::steady_clock::time_point now);
 
     // A Run is a sequence of CachedSets, which is a candidate for flattening into a single
-    // CachedSet. Because it is wasteful to flatten 1 CachedSet, a Run must contain more than 1
-    // CachedSet
+    // CachedSet. Because it is wasteful to flatten 1 CachedSet, a run must contain more than
+    // 1 CachedSet or be used for a hole punch.
     class Run {
     public:
         // A builder for a Run, to aid in construction
@@ -133,7 +133,13 @@
 
             // Builds a Run instance, if a valid Run may be built.
             std::optional<Run> validateAndBuild() {
-                if (mLengths.size() <= 1) {
+                if (mLengths.size() == 0) {
+                    return std::nullopt;
+                }
+                // Runs of length 1 which are hole punch candidates are allowed if the candidate is
+                // going to be used.
+                if (mLengths.size() == 1 &&
+                    (!mHolePunchCandidate || !(mHolePunchCandidate->requiresHolePunch()))) {
                     return std::nullopt;
                 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index f5cfd2f..fc9d0dd 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -659,6 +659,73 @@
     EXPECT_EQ(peekThroughLayer1, peekThroughLayer2);
 }
 
+// A test that verifies the hole puch optimization can be done on a single layer.
+TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+
+    // An opaque static background
+    auto& layerState0 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer0 = layerState0->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // a rounded updating layer
+    auto& layerState1 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    std::vector<LayerFE::LayerSettings> clientCompositionList = {
+            LayerFE::LayerSettings{},
+    };
+    clientCompositionList[0].source.buffer.buffer = std::make_shared<
+            renderengine::ExternalTexture>(mTestLayers[1]->layerFECompositionState.buffer,
+                                           mRenderEngine,
+                                           renderengine::ExternalTexture::Usage::READABLE);
+    EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(clientCompositionList));
+
+    const std::vector<const LayerState*> layers = {
+            layerState0.get(),
+            layerState1.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // layer 1 satisfies every condition in CachedSet::requiresHolePunch()
+    mTime += 200ms;
+    layerState1->resetFramesSinceBufferUpdate(); // it is updating
+
+    initializeOverrideBuffer(layers);
+    // Expect no cache invalidation the first time (there's no cache yet)
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the
+    // exception that there would be a hole punch above it.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer0);
+
+    // This time we merge the CachedSet in and we should still have only two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    EXPECT_NE(nullptr, overrideBuffer0); // got overridden
+    EXPECT_EQ(nullptr, overrideBuffer1); // did not
+
+    // expect 0's peek though layer to be 1's output layer
+    const auto* peekThroughLayer0 =
+            layerState0->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    const auto* peekThroughLayer1 =
+            layerState1->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    EXPECT_EQ(&mTestLayers[1]->outputLayer, peekThroughLayer0);
+    EXPECT_EQ(nullptr, peekThroughLayer1);
+}
+
 TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) {
     auto& layerState1 = mTestLayers[0]->layerState;