Improve surface visibility recovering with transition

For example:
 1. A transition contains an incorrect info which will set X
    to be invisible.
 2. When the transition is playing, set the surface of X to
    visible directly if there is no collecting transition.
 3. The transition of step 1 finishes, which applies invisible
    to X, then the recovering of step 2 doesn't help.

So it will be more robust to recover until the playing transitions
are finished. Then it can also cover some cases of other visibility
validators.

Bug: 303613521
Test: Finish several activities when switching display.
      The surface of foreground activity and its parent should
      keep visible.
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3fc49ae16be0c421a9afa6fcfaa5d771747f059d)
Merged-In: I2ecb714ef0eaf94f8950bd4cd3857abfb1c46fbc
Change-Id: I2ecb714ef0eaf94f8950bd4cd3857abfb1c46fbc
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index cd0a4ef..d570c9e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5346,18 +5346,11 @@
                 // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
                 if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) {
-                    Slog.e(TAG, "setVisibility=" + visible
-                            + " while transition is not collecting or finishing "
-                            + this + " caller=" + Debug.getCallers(8));
-                    // Force showing the parents because they may be hidden by previous transition.
                     if (visible) {
-                        final Transaction t = getSyncTransaction();
-                        for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent;
-                                p = p.getParent()) {
-                            if (p.mSurfaceControl != null) {
-                                t.show(p.mSurfaceControl);
-                            }
-                        }
+                        mTransitionController.onVisibleWithoutCollectingTransition(this,
+                                Debug.getCallers(1, 1));
+                    } else {
+                        Slog.w(TAG, "Set invisible without transition " + this);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e7c6ff4..7ef90fe 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1374,7 +1374,6 @@
             dc.handleCompleteDeferredRemoval();
         }
         validateKeyguardOcclusion();
-        validateVisibility();
 
         mState = STATE_FINISHED;
         // Rotation change may be deferred while there is a display change transition, so check
@@ -2734,29 +2733,6 @@
         }
     }
 
-    private void validateVisibility() {
-        for (int i = mTargets.size() - 1; i >= 0; --i) {
-            if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
-                return;
-            }
-        }
-        // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly.
-        // If the window container should be visible, then recover it.
-        mController.mStateValidators.add(() -> {
-            for (int i = mTargets.size() - 1; i >= 0; --i) {
-                final ChangeInfo change = mTargets.get(i);
-                if (!change.mContainer.isVisibleRequested()
-                        || change.mContainer.mSurfaceControl == null) {
-                    continue;
-                }
-                Slog.e(TAG, "Force show for visible " + change.mContainer
-                        + " which may be hidden by transition unexpectedly");
-                change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl);
-                change.mContainer.scheduleAnimation();
-            }
-        });
-    }
-
     /**
      * Returns {@code true} if the transition and the corresponding transaction should be applied
      * on display thread. Currently, this only checks for display rotation change because the order
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a43a6f6..577e755 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -951,6 +951,36 @@
         mValidateDisplayVis.clear();
     }
 
+    void onVisibleWithoutCollectingTransition(WindowContainer<?> wc, String caller) {
+        final boolean isPlaying = !mPlayingTransitions.isEmpty();
+        Slog.e(TAG, "Set visible without transition " + wc + " playing=" + isPlaying
+                + " caller=" + caller);
+        if (!isPlaying) {
+            enforceSurfaceVisible(wc);
+            return;
+        }
+        // Update surface visibility after the playing transitions are finished, so the last
+        // visibility won't be replaced by the finish transaction of transition.
+        mStateValidators.add(() -> {
+            if (wc.isVisibleRequested()) {
+                enforceSurfaceVisible(wc);
+            }
+        });
+    }
+
+    private void enforceSurfaceVisible(WindowContainer<?> wc) {
+        if (wc.mSurfaceControl == null) return;
+        wc.getSyncTransaction().show(wc.mSurfaceControl);
+        // Force showing the parents because they may be hidden by previous transition.
+        for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent;
+                p = p.getParent()) {
+            if (p.mSurfaceControl != null) {
+                p.getSyncTransaction().show(p.mSurfaceControl);
+            }
+        }
+        wc.scheduleAnimation();
+    }
+
     /**
      * Called when the transition has a complete set of participants for its operation. In other
      * words, it is when the transition is "ready" but is still waiting for participants to draw.