[sf] recover when layer snapshot is null

Updates to frontend may require CE to recalculate visble regions.
SF keeps track of this state using the mVisibleRegionsDirty flag.
If this flag is not set, this usually indicates the next frame consists
of buffer updates and the flag is used to skip uncessary work during
composition. However, if we fail to set this flag, we may end in a
scenario where CE uses stale data and ends up accessing a null
snapshot.
This is an unexpected scenario but instead of crashing, capture
traces to disk and recover gracefully by forcing CE to rebuild
layer stack. The traces can be used to reconstruct the scenario
which lead to the inconsistent state.

Test: presubmit
Bug: 295069311
Change-Id: I4331aef37f775ef48aab380ac082ca878f4d1708
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a9a2133..46374a4 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2621,19 +2621,24 @@
     constexpr bool kCursorOnly = false;
     const auto layers = moveSnapshotsToCompositionArgs(refreshArgs, kCursorOnly);
 
-    if (mLayerLifecycleManagerEnabled && !refreshArgs.updatingGeometryThisFrame) {
+    if (mLayerLifecycleManagerEnabled && !mVisibleRegionsDirty) {
         for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
             auto compositionDisplay = display->getCompositionDisplay();
             if (!compositionDisplay->getState().isEnabled) continue;
             for (auto outputLayer : compositionDisplay->getOutputLayersOrderedByZ()) {
-                LLOG_ALWAYS_FATAL_WITH_TRACE_IF(outputLayer->getLayerFE().getCompositionState() ==
-                                                        nullptr,
-                                                "Output layer %s for display %s %" PRIu64
-                                                " has a null "
-                                                "snapshot.",
-                                                outputLayer->getLayerFE().getDebugName(),
-                                                compositionDisplay->getName().c_str(),
-                                                compositionDisplay->getId().value);
+                if (outputLayer->getLayerFE().getCompositionState() == nullptr) {
+                    // This is unexpected but instead of crashing, capture traces to disk
+                    // and recover gracefully by forcing CE to rebuild layer stack.
+                    ALOGE("Output layer %s for display %s %" PRIu64 " has a null "
+                          "snapshot. Forcing mVisibleRegionsDirty",
+                          outputLayer->getLayerFE().getDebugName(),
+                          compositionDisplay->getName().c_str(), compositionDisplay->getId().value);
+
+                    TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false);
+                    mVisibleRegionsDirty = true;
+                    refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
+                    refreshArgs.updatingGeometryThisFrame = mVisibleRegionsDirty;
+                }
             }
         }
     }