Clean-up change transition when cancelled before anim starts

Change transitions require the app window be put in an interim
state while waiting for the end-state snapshot and animation
to start. when moving an app from one display to another, it
gets a config/mode change before onDisplayChanged causing it
to prepare for an animation that never happens.

This change cleans-up the pending-change-transition state in
the various cases where the animation is cancelled before it
has started.

Bug: 120034483
Test: create simulated display in desktop mode, launch freeform app
      on it, remove display. App is now visible.
      Added more AppChangeTransitionTests for these cases.

Change-Id: I14a758957903ff7b3efd286f713f81b9de293fcb
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 2cc85e2..bcf6aba 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1275,7 +1275,19 @@
     void onDisplayChanged(DisplayContent dc) {
         DisplayContent prevDc = mDisplayContent;
         super.onDisplayChanged(dc);
-        if (prevDc != null && prevDc.mFocusedApp == this) {
+        if (prevDc == null) {
+            return;
+        }
+        if (prevDc.mChangingApps.contains(this)) {
+            // This gets called *after* the AppWindowToken has been reparented to the new display.
+            // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN),
+            // so this token is now "frozen" while waiting for the animation to start on prevDc
+            // (which will be cancelled since the window is no-longer a child). However, since this
+            // is no longer a child of prevDc, this won't be notified of the cancelled animation,
+            // so we need to cancel the change transition here.
+            clearChangeLeash(getPendingTransaction(), true /* cancel */);
+        }
+        if (prevDc.mFocusedApp == this) {
             prevDc.setFocusedApp(null);
             final TaskStack stack = dc.getTopStack();
             if (stack != null) {
@@ -1584,7 +1596,10 @@
     }
 
     private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
-        if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) {
+        if (mWmService.mDisableTransitionAnimation
+                || !isVisible()
+                || getDisplayContent().mAppTransition.isTransitionSet()
+                || getSurfaceControl() == null) {
             return false;
         }
         // Only do an animation into and out-of freeform mode for now. Other mode
@@ -2561,9 +2576,7 @@
             return;
         } else if (mTransitChangeLeash != null) {
             // unparent mTransitChangeLeash for clean-up
-            t.hide(mTransitChangeLeash);
-            t.reparent(mTransitChangeLeash, null);
-            mTransitChangeLeash = null;
+            clearChangeLeash(t, false /* cancel */);
         }
 
         if (mAnimatingAppWindowTokenRegistry != null) {
@@ -2659,15 +2672,36 @@
         return super.isSelfAnimating();
     }
 
+    /**
+     * @param cancel {@code true} if clearing the leash due to cancelling instead of transferring
+     *                            to another leash.
+     */
+    private void clearChangeLeash(Transaction t, boolean cancel) {
+        if (mTransitChangeLeash == null) {
+            return;
+        }
+        if (cancel) {
+            clearThumbnail();
+            SurfaceControl sc = getSurfaceControl();
+            SurfaceControl parentSc = getParentSurfaceControl();
+            // Don't reparent if surface is getting destroyed
+            if (parentSc != null && sc != null) {
+                t.reparent(sc, getParentSurfaceControl());
+            }
+        }
+        t.hide(mTransitChangeLeash);
+        t.reparent(mTransitChangeLeash, null);
+        mTransitChangeLeash = null;
+        if (cancel) {
+            onAnimationLeashDestroyed(t);
+        }
+    }
+
     @Override
     void cancelAnimation() {
         cancelAnimationOnly();
         clearThumbnail();
-        if (mTransitChangeLeash != null) {
-            getPendingTransaction().hide(mTransitChangeLeash);
-            getPendingTransaction().reparent(mTransitChangeLeash, null);
-            mTransitChangeLeash = null;
-        }
+        clearChangeLeash(getPendingTransaction(), true /* cancel */);
     }
 
     /**
@@ -3003,7 +3037,9 @@
     void removeFromPendingTransition() {
         if (isWaitingForTransitionStart() && mDisplayContent != null) {
             mDisplayContent.mOpeningApps.remove(this);
-            mDisplayContent.mChangingApps.remove(this);
+            if (mDisplayContent.mChangingApps.remove(this)) {
+                clearChangeLeash(getPendingTransaction(), true /* cancel */);
+            }
             mDisplayContent.mClosingApps.remove(this);
         }
     }