Ensure that a PIP activity gets stopped before MW/PIP mode changes.

- Adds a workaround to ensure that onStop() is dispatched to the PIP
  activities when the PIP is dismissed
- Consolidating dismiss PIP behavior to call moveTaskToFullscreen().
  Now we are consistent, and when we expand the PIP, we call
  moveTaskToFullscreen(top), and when dismissing the PIP we call
  moveTaskToFullscreen(bottom)
- Fixing issue where we were dispatching extra MW/PIP mode changes when
  just expanding the PIP
- Fixing typo in last commit where PIP mode change was not getting
  scheduled correctly
- Fixing issue where move-to-fullscreen log was sent for each task

Bug: 36567036
Test: android.server.cts.ActivityManagerPinnedStackTests
Test: #testStopBeforeMultiWindowCallbacksOnDismiss

Change-Id: I303b88d75e9f136da0c238ef5987804e0684c78e
Signed-off-by: Winson Chung <winsonc@google.com>
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index ecf5647..1d589e6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -42,6 +42,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
@@ -166,7 +167,6 @@
 
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
@@ -2321,7 +2321,6 @@
         mWindowManager.deferSurfaceLayout();
         try {
             if (fromStackId == DOCKED_STACK_ID) {
-
                 // We are moving all tasks from the docked stack to the fullscreen stack,
                 // which is dismissing the docked stack, so resize all other stacks to
                 // fullscreen here already so we don't end up with resize trashing.
@@ -2340,13 +2339,19 @@
                 // resize when we remove task from it below and it is detached from the
                 // display because it no longer contains any tasks.
                 mAllowDockedStackResize = false;
+            } else if (fromStackId == PINNED_STACK_ID) {
+                if (onTop) {
+                    // Log if we are expanding the PiP to fullscreen
+                    MetricsLogger.action(mService.mContext,
+                            ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
+                }
             }
             ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
             final boolean isFullscreenStackVisible = fullscreenStack != null &&
                     fullscreenStack.shouldBeVisible(null) == STACK_VISIBLE;
             // If we are moving from the pinned stack, then the animation takes care of updating
             // the picture-in-picture mode.
-            final boolean schedulePictureInPictureModeChange = (fromStackId != PINNED_STACK_ID);
+            final boolean schedulePictureInPictureModeChange = (fromStackId == PINNED_STACK_ID);
             final ArrayList<TaskRecord> tasks = stack.getAllTasks();
             final int size = tasks.size();
             if (onTop) {
@@ -2359,8 +2364,6 @@
                         // pinned stack is recreated. See moveActivityToPinnedStackLocked().
                         task.setTaskToReturnTo(isFullscreenStackVisible && onTop ?
                                 APPLICATION_ACTIVITY_TYPE : HOME_ACTIVITY_TYPE);
-                        MetricsLogger.action(mService.mContext,
-                                MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
                     }
                     // Defer resume until all the tasks have been moved to the fullscreen stack
                     task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
@@ -2515,27 +2518,24 @@
 
         final ArrayList<TaskRecord> tasks = stack.getAllTasks();
         if (stack.getStackId() == PINNED_STACK_ID) {
-            final ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
-            if (fullscreenStack != null) {
-                final boolean isFullscreenStackVisible =
-                        fullscreenStack.shouldBeVisible(null) == STACK_VISIBLE;
-                for (int i = 0; i < tasks.size(); i++) {
-                    // Insert the task either at the top of the fullscreen stack if it is hidden,
-                    // or to the bottom if it is currently visible
-                    final int insertPosition = isFullscreenStackVisible ? 0
-                            : fullscreenStack.getChildCount();
-                    final TaskRecord task = tasks.get(i);
-                    // Defer resume until we remove all the tasks
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition,
-                            REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME, "removeStack");
-                }
-                ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-                resumeFocusedStackTopActivityLocked();
-            } else {
-                // If there is no fullscreen stack, then create the stack and move all the tasks
-                // onto the stack
-                moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
-            }
+            /**
+             * Workaround: Force-stop all the activities in the pinned stack before we reparent them
+             * to the fullscreen stack.  This is to guarantee that when we are removing a stack,
+             * that the client receives onStop() before it is reparented.  We do this by detaching
+             * the stack from the display so that it will be considered invisible when
+             * ensureActivitiesVisibleLocked() is called, and all of its activitys will be marked
+             * invisible as well and added to the stopping list.  After which we process the
+             * stopping list by handling the idle.
+             */
+            final PinnedActivityStack pinnedStack = (PinnedActivityStack) stack;
+            pinnedStack.mForceHidden = true;
+            pinnedStack.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+            pinnedStack.mForceHidden = false;
+            activityIdleInternalLocked(null, false /* fromTimeout */,
+                    true /* processPausingActivites */, null /* configuration */);
+
+            // Move all the tasks to the bottom of the fullscreen stack
+            moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
         } else {
             for (int i = tasks.size() - 1; i >= 0; i--) {
                 removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
@@ -2904,6 +2904,10 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
+        // TODO(b/36099777): Schedule the PiP mode change here immediately until we can defer all
+        // callbacks until after the bounds animation
+        scheduleUpdatePictureInPictureModeIfNeeded(r.getTask(), destBounds, true /* immediate */);
+
         stack.animateResizePinnedStack(sourceBounds, destBounds, -1 /* animationDuration */);
         mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName);
     }