Transfer starting windows more aggressively

We currently transfer starting windows when starting another
activity on top of the previous one. However, that's not enough:

Sometimes we also need to transfer back in case the one on top
finishes itself/gets hidden for other reasons.

This fixes an issue where some animations are aborted in the
middle of the animation because the trampoline activity contained
the starting window but then the trampoline activity was finished.

Test: go/wm-smoke
Test: Open Drive, Reopen Drive 100x
Test: Open Hangouts, Receive notification, open it, reopen
hangouts from launcher 100x
Test: AppWindowContainerControllerTests
Bug: 72301120

Change-Id: I6711ce0bc35de430aac03f7b8b39ceac26e595e7
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 40f772a..1170148 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -379,6 +379,8 @@
 
                 if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "No longer Stopped: " + wtoken);
                 wtoken.mAppStopped = false;
+
+                mContainer.transferStartingWindowFromHiddenAboveTokenIfNeeded();
             }
 
             // If we are preparing an app transition, then delay changing
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 2672337..b2f153a 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -187,6 +187,7 @@
     StartingSurface startingSurface;
     boolean startingDisplayed;
     boolean startingMoved;
+
     // True if the hidden state of this token was forced to false due to a transferred starting
     // window.
     private boolean mHiddenSetFromTransferredStartingWindow;
@@ -1136,6 +1137,25 @@
         stopFreezingScreen(true, true);
     }
 
+    /**
+     * Tries to transfer the starting window from a token that's above ourselves in the task but
+     * not visible anymore. This is a common scenario apps use: Trampoline activity T start main
+     * activity M in the same task. Now, when reopening the task, T starts on top of M but then
+     * immediately finishes after, so we have to transfer T to M.
+     */
+    void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
+        final Task task = getTask();
+        for (int i = task.mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken fromToken = task.mChildren.get(i);
+            if (fromToken == this) {
+                return;
+            }
+            if (fromToken.hiddenRequested && transferStartingWindow(fromToken.token)) {
+                return;
+            }
+        }
+    }
+
     boolean transferStartingWindow(IBinder transferFrom) {
         final AppWindowToken fromToken = getDisplayContent().getAppWindowToken(transferFrom);
         if (fromToken == null) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 4179590..40ee552 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -559,7 +559,8 @@
                         + wtoken.allDrawn + " startingDisplayed="
                         + wtoken.startingDisplayed + " startingMoved="
                         + wtoken.startingMoved + " isRelaunching()="
-                        + wtoken.isRelaunching());
+                        + wtoken.isRelaunching() + " startingWindow="
+                        + wtoken.startingWindow);
 
 
                 final boolean allDrawn = wtoken.allDrawn && !wtoken.isRelaunching();
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 76e4e89..e0645b1 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -20,11 +20,9 @@
 import org.junit.Test;
 
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.SecurityTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.WindowManager;
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -36,13 +34,12 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
-import java.util.function.Consumer;
+import com.android.server.wm.WindowTestUtils.TestTaskWindowContainerController;
 
 /**
  * Test class for {@link AppWindowContainerController}.
  *
- * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.wm.AppWindowContainerControllerTests
+ * atest FrameworksServicesTests:com.android.server.wm.AppWindowContainerControllerTests
  */
 @SmallTest
 @Presubmit
@@ -176,6 +173,33 @@
     }
 
     @Test
+    public void testTryTransferStartingWindowFromHiddenAboveToken() throws Exception {
+
+        // Add two tasks on top of each other.
+        TestTaskWindowContainerController taskController =
+                new WindowTestUtils.TestTaskWindowContainerController(this);
+        final WindowTestUtils.TestAppWindowContainerController controllerTop =
+                createAppWindowController(taskController);
+        final WindowTestUtils.TestAppWindowContainerController controllerBottom =
+                createAppWindowController(taskController);
+
+        // Add a starting window.
+        controllerTop.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+                android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
+                false, false);
+        waitUntilHandlersIdle();
+
+        // Make the top one invisible, and try transfering the starting window from the top to the
+        // bottom one.
+        controllerTop.setVisibility(false, false);
+        controllerBottom.mContainer.transferStartingWindowFromHiddenAboveTokenIfNeeded();
+
+        // Assert that the bottom window now has the starting window.
+        assertNoStartingWindow(controllerTop.getAppWindowToken(mDisplayContent));
+        assertHasStartingWindow(controllerBottom.getAppWindowToken(mDisplayContent));
+    }
+
+    @Test
     public void testReparent() throws Exception {
         final StackWindowController stackController =
             createStackControllerOnDisplay(mDisplayContent);