Start PiP dismiss from SysUI via TaskOrganizer

following ag/10570572, dismiss PiP is driven by SysUI as following
- SysUI issues WindowContainerTransaction and set the child windowing
mode at the beginning
- SysUI continues the animation
- SysUI issues WindowContainerTransaction and set the final windowing
mode at the end

This solves also the black background issue while exiting from PiP to
fullscreen mode.

Known issues
- app does not receive fullscreen configuration when transitioning from
PiP to fullscreen mode
- saving of the reentry bounds should be originated from SysUI rather
than WM going forward

Bug: 149947030
Bug: 151866274
Test: atest PinnedStackTests PipAnimationControllerTest
Change-Id: I7ed0d8b47dcc26653ebe2f3c08acab9e8b835db4
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 03717ec..53f5721 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -298,13 +298,6 @@
     void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
             in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
 
-    /**
-     * Dismisses PiP
-     * @param animate True if the dismissal should be animated.
-     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
-     *                          default animation duration should be used.
-     */
-    void dismissPip(boolean animate, int animationDuration);
     void suppressResizeConfigChanges(boolean suppress);
     void moveTasksToFullscreenStack(int fromStackId, boolean onTop);
     boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 6ce5e7c..af8b184 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.pip;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
 import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
@@ -26,8 +29,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.window.ITaskOrganizerController;
 import android.app.PictureInPictureParams;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -38,9 +39,9 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Size;
+import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainer;
-import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
@@ -216,6 +217,29 @@
         mOneShotAnimationType = animationType;
     }
 
+    /**
+     * Dismiss PiP, this is done in two phases using {@link WindowContainerTransaction}
+     * - setActivityWindowingMode to fullscreen at beginning of the transaction. without changing
+     *   the windowing mode of the Task itself. This makes sure the activity render it's fullscreen
+     *   configuration while the Task is still in PiP.
+     * - setWindowingMode to fullscreen at the end of transition
+     * @param animationDurationMs duration in millisecond for the exiting PiP transition
+     */
+    public void dismissPip(int animationDurationMs) {
+        try {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN);
+            WindowOrganizer.applyTransaction(wct);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to apply container transaction", e);
+        }
+        final Rect destinationBounds = mBoundsToRestore.remove(mToken.asBinder());
+        scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
+                TRANSITION_DIRECTION_TO_FULLSCREEN, animationDurationMs,
+                null /* updateBoundsCallback */);
+        mInPip = false;
+    }
+
     @Override
     public void onTaskAppeared(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(info, "Requires RunningTaskInfo");
@@ -235,7 +259,8 @@
         mBoundsToRestore.put(mToken.asBinder(), currentBounds);
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
             scheduleAnimateResizePip(currentBounds, destinationBounds,
-                    TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null);
+                    TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
+                    null /* updateBoundsCallback */);
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
             mUpdateHandler.post(() -> mPipAnimationController
                     .getAnimator(mLeash, destinationBounds, 0f, 1f)
@@ -249,6 +274,12 @@
         }
     }
 
+    /**
+     * Note that dismissing PiP is now originated from SystemUI, see {@link #dismissPip(int)}.
+     * Meanwhile this callback is invoked whenever the task is removed. For instance:
+     *   - as a result of removeStacksInWindowingModes from WM
+     *   - activity itself is died
+     */
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
         IWindowContainer token = info.token;
@@ -259,7 +290,8 @@
         }
         final Rect boundsToRestore = mBoundsToRestore.remove(token.asBinder());
         scheduleAnimateResizePip(mLastReportedBounds, boundsToRestore,
-                TRANSITION_DIRECTION_TO_FULLSCREEN, mEnterExitAnimationDuration, null);
+                TRANSITION_DIRECTION_TO_FULLSCREEN, mEnterExitAnimationDuration,
+                null /* updateBoundsCallback */);
         mInPip = false;
     }
 
@@ -274,7 +306,8 @@
                 getAspectRatioOrDefault(newParams),
                 null /* bounds */, getMinimalSize(info.topActivityInfo));
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
-        scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration, null);
+        scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
+                null /* updateBoundsCallback */);
     }
 
     /**
@@ -434,12 +467,19 @@
         }
         mLastReportedBounds.set(destinationBounds);
         try {
-            // If we are animating to fullscreen, then we need to reset the override bounds on the
-            // task to ensure that the task "matches" the parent's bounds
-            Rect taskBounds = direction == TRANSITION_DIRECTION_TO_FULLSCREEN
-                    ? null
-                    : destinationBounds;
             final WindowContainerTransaction wct = new WindowContainerTransaction();
+            final Rect taskBounds;
+            if (direction == TRANSITION_DIRECTION_TO_FULLSCREEN) {
+                // If we are animating to fullscreen, then we need to reset the override bounds
+                // on the task to ensure that the task "matches" the parent's bounds, this applies
+                // also to the final windowing mode, which should be reset to undefined rather than
+                // fullscreen.
+                taskBounds = null;
+                wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED)
+                        .setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+            } else {
+                taskBounds = destinationBounds;
+            }
             if (direction == TRANSITION_DIRECTION_TO_PIP) {
                 wct.scheduleFinishEnterPip(mToken, taskBounds);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 449a2bc..7974281 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -219,11 +219,7 @@
         cancelAnimations();
         mMenuController.hideMenuWithoutResize();
         mPipTaskOrganizer.getUpdateHandler().post(() -> {
-            try {
-                mActivityTaskManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error expanding PiP activity", e);
-            }
+            mPipTaskOrganizer.dismissPip(skipAnimation ? 0 : EXPAND_STACK_TO_FULLSCREEN_DURATION);
         });
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 214a676..14c82b3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -144,7 +144,6 @@
 import android.app.IAssistDataReceiver;
 import android.app.INotificationManager;
 import android.app.IRequestFinishCallback;
-import android.window.ITaskOrganizerController;
 import android.app.ITaskStackListener;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -230,9 +229,9 @@
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
+import android.view.WindowManager;
 import android.window.IWindowOrganizerController;
 import android.window.WindowContainerTransaction;
-import android.view.WindowManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -3979,35 +3978,6 @@
         }
     }
 
-    /**
-     * Dismisses Pip
-     * @param animate True if the dismissal should be animated.
-     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
-     *                          default animation duration should be used.
-     */
-    @Override
-    public void dismissPip(boolean animate, int animationDuration) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                final ActivityStack stack =
-                        mRootWindowContainer.getDefaultDisplay().getRootPinnedTask();
-                if (stack == null) {
-                    Slog.w(TAG, "dismissPip: pinned stack not found.");
-                    return;
-                }
-                if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
-                    throw new IllegalArgumentException("Stack: " + stack
-                            + " doesn't support animated resize.");
-                }
-                stack.dismissPip();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
     @Override
     public void suppressResizeConfigChanges(boolean suppress) throws RemoteException {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "suppressResizeConfigChanges()");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 66566bc..0dee6f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1115,7 +1115,6 @@
                 () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
         assertSecurityException(expectCallable,
                 () -> mService.setTaskWindowingModeSplitScreenPrimary(0, true));
-        assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
         assertSecurityException(expectCallable,
                 () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
         assertSecurityException(expectCallable, () -> mService.getAllStackInfos());