Merge "Fix transition wait when ActivityEmbedding enters PiP" into tm-dev
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index cefc871..3bda2e6 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -562,9 +562,6 @@
                 leafTask = null;
                 break;
             }
-            // The activity may be a child of embedded Task, but we want to find the owner Task.
-            // As a result, find the organized TaskFragment first.
-            final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
             // There are also cases where the Task contains non-embedded activity, such as launching
             // split TaskFragments from a non-embedded activity.
             // The hierarchy may looks like this:
@@ -575,10 +572,9 @@
             //    - TaskFragment
             //       - Activity
             // We also want to have the organizer handle the transition for such case.
-            final Task task = organizedTaskFragment != null
-                    ? organizedTaskFragment.getTask()
-                    : r.getTask();
-            if (task == null) {
+            final Task task = r.getTask();
+            // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
+            if (task == null || task.inPinnedWindowingMode()) {
                 leafTask = null;
                 break;
             }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0be29a9..ca4c450 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2097,11 +2097,14 @@
             r.setWindowingMode(intermediateWindowingMode);
             r.mWaitForEnteringPinnedMode = true;
             rootTask.forAllTaskFragments(tf -> {
-                // When the Task is entering picture-in-picture, we should clear all override from
-                // the client organizer, so the PIP activity can get the correct config from the
-                // Task, and prevent conflict with the PipTaskOrganizer.
-                if (tf.isOrganizedTaskFragment()) {
-                    tf.resetAdjacentTaskFragment();
+                if (!tf.isOrganizedTaskFragment()) {
+                    return;
+                }
+                tf.resetAdjacentTaskFragment();
+                if (tf.getTopNonFinishingActivity() != null) {
+                    // When the Task is entering picture-in-picture, we should clear all override
+                    // from the client organizer, so the PIP activity can get the correct config
+                    // from the Task, and prevent conflict with the PipTaskOrganizer.
                     tf.updateRequestedOverrideConfiguration(EMPTY);
                 }
             });
@@ -2116,7 +2119,8 @@
             // to the root pinned task
             r.supportsEnterPipOnTaskSwitch = false;
 
-            if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip) {
+            if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip
+                    && organizedTf.isTaskVisibleRequested()) {
                 // Dispatch the pending info to TaskFragmentOrganizer before PIP animation.
                 // Otherwise, it will keep waiting for the empty TaskFragment to be non-empty.
                 mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e0346544..c78d4cb 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2302,11 +2302,32 @@
         return mTaskFragmentOrganizer != null;
     }
 
+    /** Whether the Task should be visible. */
+    boolean isTaskVisibleRequested() {
+        final Task task = getTask();
+        return task != null && task.isVisibleRequested();
+    }
+
     boolean isReadyToTransit() {
+        // We only wait when this is organized to give the organizer a chance to update.
+        if (!isOrganizedTaskFragment()) {
+            return true;
+        }
         // We don't want to start the transition if the organized TaskFragment is empty, unless
         // it is requested to be removed.
-        return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null
-                || mIsRemovalRequested;
+        if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+            return true;
+        }
+        // Organizer shouldn't change embedded TaskFragment in PiP.
+        if (isEmbeddedTaskFragmentInPip()) {
+            return true;
+        }
+        // The TaskFragment becomes empty because the last running activity enters PiP when the Task
+        // is minimized.
+        if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) {
+            return true;
+        }
+        return false;
     }
 
     /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
@@ -2424,8 +2445,19 @@
         mIsRemovalRequested = false;
         resetAdjacentTaskFragment();
         cleanUp();
+        final boolean shouldExecuteAppTransition =
+                mClearedTaskFragmentForPip && isTaskVisibleRequested();
         super.removeImmediately();
         sendTaskFragmentVanished();
+        if (shouldExecuteAppTransition && mDisplayContent != null) {
+            // When the Task is still visible, and the TaskFragment is removed because the last
+            // running activity is reparenting to PiP, it is possible that no activity is getting
+            // paused or resumed (having an embedded activity in split), thus we need to relayout
+            // and execute it explicitly.
+            mAtmService.addWindowLayoutReasons(
+                    ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
+            mDisplayContent.executeAppTransition();
+        }
     }
 
     /** Called on remove to cleanup. */
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index bd351ac..764960a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -518,8 +518,13 @@
         // longer has activities. As a result, the organizer will never get this info changed event
         // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
         // has running activities.
+        // Another case is when an organized TaskFragment became empty because the last running
+        // activity is reparented to a new Task due to enter PiP. We also want to notify the
+        // organizer, so it can remove the empty TaskFragment and update the paired TaskFragment
+        // without causing the extra delay.
         return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
-                && task.topRunningActivity() == null && lastInfo != null
+                && (task.topRunningActivity() == null || info.isTaskFragmentClearedForPip())
+                && lastInfo != null
                 && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 54fa4e4..240943c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -19,8 +19,10 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -30,6 +32,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 
 import android.content.res.Configuration;
@@ -257,6 +260,9 @@
                 .createActivityCount(1)
                 .build();
         final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
+        final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
+        activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
+        activity1.setVisibility(true /* visible */, false /* deferHidingClient */);
         spyOn(mAtm.mTaskFragmentOrganizerController);
 
         // Move activity to pinned.
@@ -269,11 +275,47 @@
         final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo();
         assertTrue(info.isTaskFragmentClearedForPip());
         assertTrue(info.isEmpty());
+
+        // Notify organizer because the Task is still visible.
+        assertTrue(task.isVisibleRequested());
         verify(mAtm.mTaskFragmentOrganizerController)
                 .dispatchPendingInfoChangedEvent(taskFragment0);
     }
 
     @Test
+    public void testIsReadyToTransit() {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
+                .build();
+        final Task task = taskFragment.getTask();
+
+        // Not ready when it is empty.
+        assertFalse(taskFragment.isReadyToTransit());
+
+        // Ready when it is not empty.
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+        doNothing().when(activity).setDropInputMode(anyInt());
+        activity.reparent(taskFragment, WindowContainer.POSITION_TOP);
+        assertTrue(taskFragment.isReadyToTransit());
+
+        // Ready when the Task is in PiP.
+        taskFragment.removeChild(activity);
+        task.setWindowingMode(WINDOWING_MODE_PINNED);
+        assertTrue(taskFragment.isReadyToTransit());
+
+        // Ready when the TaskFragment is empty because of PiP, and the Task is invisible.
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskFragment.mClearedTaskFragmentForPip = true;
+        assertTrue(taskFragment.isReadyToTransit());
+
+        // Not ready if the task is still visible when the TaskFragment becomes empty.
+        doReturn(true).when(task).isVisibleRequested();
+        assertFalse(taskFragment.isReadyToTransit());
+    }
+
+    @Test
     public void testActivityHasOverlayOverUntrustedModeEmbedded() {
         final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
                 ACTIVITY_TYPE_STANDARD);