Move bounds calculation and snap fraction to SysUI

Major changes in this CL

- Added PipBoundsHandler in SysUI package handles the bounds calculation
and save/restore the snap fraction
- Added IPinnedStackListener.onSaveReentrySnapFraction and
IPinnedStackListener.onResetReentrySnapFration, both called from
PinnedStackController (WM) to SysUI
- Added IPinnedStackListener.onPrepareAnimation, called from WM to
SysUI to notify that an animation for pinned stack is about to happen
- Added IPinnedStackController.startAnimation, called from SysUI to WM
to actually start the pinned stack animation
- Updated tv/PipManager to use PipBoundsHandler as well

Known issues and what's next

- Shelf height is set after the animation is started, therefore one may
notice the PiP window is hovering over the shelf. This is tracked
separatedly by b/139016518
- Bounds calculations still exist in PinnedStackController since
onTaskStackChanged callback requires so. Once we addressed b/139016833
(move rotation bounds calculation to SysUI), they should be all gone.
- Move the pinned stack animation from WM to SysUI and we can furthre
remove IPinnedStackListener.onPrepareAnimation and
IPinnedStackController.startAnimation

More details: go/pip-sysui-migration

Bug: 139015463
Bug: 139015142
Test: atest PinnedStackTests
Test: atest PinnedStackControllerTest
Change-Id: Ib62d8152ecc7b18a671ec403af44cc517612169e
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index d2dcb56..f1d152b 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.graphics.Rect;
+
 /**
  * An interface to the PinnedStackController to update it of state changes, and to query
  * information based on the current state.
@@ -30,15 +32,17 @@
     oneway void setIsMinimized(boolean isMinimized);
 
     /**
-     * Notifies the controller of the current min edge size, this is needed to allow the system to
-     * properly calculate the aspect ratio of the expanded PIP.  The given {@param minEdgeSize} is
-     * always bounded to be larger than the default minEdgeSize, so the caller can call this method
-     * with 0 to reset to the default size.
-     */
-    oneway void setMinEdgeSize(int minEdgeSize);
-
-    /**
      * @return what WM considers to be the current device rotation.
      */
     int getDisplayRotation();
+
+    /**
+     * Notifies the controller to actually start the PiP animation.
+     * The bounds would be calculated based on the last save reentry fraction internally.
+     * {@param destinationBounds} is the stack bounds of the final PiP window
+     * and {@param sourceRectHint} is the source bounds hint used when entering picture-in-picture,
+     * expect the same bound passed via IPinnedStackListener#onPrepareAnimation.
+     * {@param animationDuration} suggests the animation duration transitioning to PiP window.
+     */
+    void startAnimation(in Rect destinationBounds, in Rect sourceRectHint, int animationDuration);
 }
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 2da353b..806d81e 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -16,8 +16,10 @@
 
 package android.view;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 
 /**
@@ -36,18 +38,13 @@
     /**
      * Called when the window manager has detected a change that would cause the movement bounds
      * to be changed (ie. after configuration change, aspect ratio change, etc). It then provides
-     * the components that allow the listener to calculate the movement bounds itself. The
-     * {@param normalBounds} are also the default bounds that the PiP would be entered in its
-     * current state with the aspect ratio applied.  The {@param animatingBounds} are provided
-     * to indicate the current target bounds of the pinned stack (the final bounds if animating,
-     * the current bounds if not), which may be helpful in calculating dependent animation bounds.
-     *
-     * The {@param displayRotation} is provided so that the client can verify when making certain
-     * calls that it will not provide stale information based on an old display rotation (ie. if
-     * the WM has changed in the mean time but the client has not received onMovementBoundsChanged).
+     * the components that allow the listener to calculate the movement bounds itself.
+     * The {@param animatingBounds} are provided to indicate the current target bounds of the
+     * pinned stack (the final bounds if animating, the current bounds if not),
+     * which may be helpful in calculating dependent animation bounds.
      */
-    void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation);
+    void onMovementBoundsChanged(in Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment);
 
     /**
      * Called when window manager decides to adjust the pinned stack bounds because of the IME, or
@@ -76,4 +73,47 @@
      * is first registered to allow the listener to synchronized its state with the controller.
      */
     void onActionsChanged(in ParceledListSlice actions);
+
+    /**
+     * Called by the window manager to notify the listener to save the reentry fraction,
+     * typically when an Activity leaves PiP (picture-in-picture) mode to fullscreen.
+     * {@param componentName} represents the application component of PiP window
+     * while {@param bounds} is the current PiP bounds used to calculate the
+     * reentry snap fraction.
+     */
+    void onSaveReentrySnapFraction(in ComponentName componentName, in Rect bounds);
+
+    /**
+     * Called by the window manager to notify the listener to reset saved reentry fraction,
+     * typically when an Activity enters PiP (picture-in-picture) mode from fullscreen.
+     * {@param componentName} represents the application component of PiP window.
+     */
+    void onResetReentrySnapFraction(in ComponentName componentName);
+
+    /**
+     * Called when the window manager has detected change on DisplayInfo,  or
+     * when the listener is first registered to allow the listener to synchronized its state with
+     * the controller.
+     */
+    void onDisplayInfoChanged(in DisplayInfo displayInfo);
+
+    /**
+     * Called by the window manager at the beginning of a configuration update cascade
+     * since the metrics from these resources are used for bounds calculations.
+     */
+    void onConfigurationChanged();
+
+    /**
+     * Called by the window manager when the aspect ratio is reset.
+     */
+    void onAspectRatioChanged(float aspectRatio);
+
+    /**
+     * Called by the window manager to notify the listener to prepare for PiP animation.
+     * Internally, the target bounds would be calculated from the given {@param aspectRatio}
+     * and {@param bounds}, the saved reentry snap fraction also contributes.
+     * Caller would wait for a IPinnedStackController#startAnimation callback to actually
+     * start the animation, see details in IPinnedStackController.
+     */
+    void onPrepareAnimation(in Rect sourceRectHint, float aspectRatio, in Rect bounds);
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
index 3ae2df5b..2797042 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
@@ -16,9 +16,10 @@
 
 package com.android.systemui.shared.system;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
-import android.os.RemoteException;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 
@@ -32,62 +33,132 @@
  * previously set listener.
  */
 public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
-    private List<IPinnedStackListener> mListeners = new ArrayList<>();
+    private List<PinnedStackListener> mListeners = new ArrayList<>();
 
     /** Adds a listener to receive updates from the WindowManagerService. */
-    public void addListener(IPinnedStackListener listener) {
+    public void addListener(PinnedStackListener listener) {
         mListeners.add(listener);
     }
 
     /** Removes a listener so it will no longer receive updates from the WindowManagerService. */
-    public void removeListener(IPinnedStackListener listener) {
+    public void removeListener(PinnedStackListener listener) {
         mListeners.remove(listener);
     }
 
     @Override
-    public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onListenerRegistered(IPinnedStackController controller) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onListenerRegistered(controller);
         }
     }
 
     @Override
-    public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
-            listener.onMovementBoundsChanged(
-                    insetBounds, normalBounds, animatingBounds,
-                    fromImeAdjustment, fromShelfAdjustment, displayRotation);
+    public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment,
+                    fromShelfAdjustment);
         }
     }
 
     @Override
-    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onImeVisibilityChanged(imeVisible, imeHeight);
         }
     }
 
     @Override
-    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
         }
     }
 
     @Override
-    public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onMinimizedStateChanged(boolean isMinimized) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onMinimizedStateChanged(isMinimized);
         }
     }
 
     @Override
-    public void onActionsChanged(ParceledListSlice actions) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onActionsChanged(ParceledListSlice actions) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onActionsChanged(actions);
         }
     }
+
+    @Override
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onSaveReentrySnapFraction(componentName, bounds);
+        }
+    }
+
+    @Override
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onResetReentrySnapFraction(componentName);
+        }
+    }
+
+    @Override
+    public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onDisplayInfoChanged(displayInfo);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged() {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onConfigurationChanged();
+        }
+    }
+
+    @Override
+    public void onAspectRatioChanged(float aspectRatio) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onAspectRatioChanged(aspectRatio);
+        }
+    }
+
+    @Override
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
+        }
+    }
+
+    /**
+     * A counterpart of {@link IPinnedStackListener} with empty implementations.
+     * Subclasses can ignore those methods they do not intend to take action upon.
+     */
+    public static class PinnedStackListener {
+        public void onListenerRegistered(IPinnedStackController controller) {}
+
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {}
+
+        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+
+        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
+
+        public void onMinimizedStateChanged(boolean isMinimized) {}
+
+        public void onActionsChanged(ParceledListSlice actions) {}
+
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {}
+
+        public void onResetReentrySnapFraction(ComponentName componentName) {}
+
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {}
+
+        public void onConfigurationChanged() {}
+
+        public void onAspectRatioChanged(float aspectRatio) {}
+
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {}
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 794c30a..9f1a1fa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,12 +27,12 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.IPinnedStackListener;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 
 public class WindowManagerWrapper {
 
@@ -212,7 +212,7 @@
      * Adds a pinned stack listener, which will receive updates from the window manager service
      * along with any other pinned stack listeners that were added via this method.
      */
-    public void addPinnedStackListener(IPinnedStackListener listener) throws RemoteException {
+    public void addPinnedStackListener(PinnedStackListener listener) throws RemoteException {
         mPinnedStackListenerForwarder.addListener(listener);
         WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
                 DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
@@ -221,7 +221,7 @@
     /**
      * Removes a pinned stack listener.
      */
-    public void removePinnedStackListener(IPinnedStackListener listener) {
+    public void removePinnedStackListener(PinnedStackListener listener) {
         mPinnedStackListenerForwarder.removeListener(listener);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 67fc3e3..43576a4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -48,7 +48,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -61,8 +60,6 @@
 import android.util.Pair;
 import android.util.SparseSetArray;
 import android.view.Display;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
@@ -76,6 +73,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -993,32 +991,12 @@
     }
 
     /** PinnedStackListener that dispatches IME visibility updates to the stack. */
-    private class BubblesImeListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) throws RemoteException {}
-
+    private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mStackView != null && mStackView.getBubbleCount() > 0) {
                 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
             }
         }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-                throws RemoteException {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {}
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) throws RemoteException {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 30be775..6795bff 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -18,6 +18,7 @@
 
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -46,9 +47,6 @@
     private static final String TAG = PipBoundsHandler.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
-    // System.identityHashCode guarantees zero for null object.
-    private static final int INVALID_SYSTEM_IDENTITY_TOKEN = 0;
-
     private final Context mContext;
     private final IWindowManager mWindowManager;
     private final PipSnapAlgorithm mSnapAlgorithm;
@@ -58,7 +56,7 @@
     private final Point mTmpDisplaySize = new Point();
 
     private IPinnedStackController mPinnedStackController;
-    private int mLastPipToken;
+    private ComponentName mLastPipComponentName;
     private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
 
     private float mDefaultAspectRatio;
@@ -80,8 +78,11 @@
         mContext = context;
         mSnapAlgorithm = new PipSnapAlgorithm(context);
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
-        mAspectRatio = mDefaultAspectRatio;
         reloadResources();
+        // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
+        // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
+        // triggers a configuration change and the resources to be reloaded.
+        mAspectRatio = mDefaultAspectRatio;
     }
 
     /**
@@ -161,27 +162,27 @@
     }
 
     /**
-     * Responds to IPinnedStackListener on saving reentry snap fraction for a given token.
-     * Token should be generated via {@link System#identityHashCode(Object)}
+     * Responds to IPinnedStackListener on saving reentry snap fraction
+     * for a given {@link ComponentName}.
      */
-    public void onSaveReentrySnapFraction(int token, Rect stackBounds) {
-        mReentrySnapFraction = getSnapFraction(stackBounds);
-        mLastPipToken = token;
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        mReentrySnapFraction = getSnapFraction(bounds);
+        mLastPipComponentName = componentName;
     }
 
     /**
-     * Responds to IPinnedStackListener on resetting reentry snap fraction for a given token.
-     * Token should be generated via {@link System#identityHashCode(Object)}
+     * Responds to IPinnedStackListener on resetting reentry snap fraction
+     * for a given {@link ComponentName}.
      */
-    public void onResetReentrySnapFraction(int token) {
-        if (mLastPipToken == token) {
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        if (componentName.equals(mLastPipComponentName)) {
             onResetReentrySnapFractionUnchecked();
         }
     }
 
     private void onResetReentrySnapFractionUnchecked() {
         mReentrySnapFraction = INVALID_SNAP_FRACTION;
-        mLastPipToken = INVALID_SYSTEM_IDENTITY_TOKEN;
+        mLastPipComponentName = null;
     }
 
     /**
@@ -212,24 +213,28 @@
     /**
      * Responds to IPinnedStackListener on preparing the pinned stack animation.
      */
-    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
-        final Rect targetStackBounds;
-        if (stackBounds == null) {
-            targetStackBounds = getDefaultBounds(mReentrySnapFraction);
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        final Rect destinationBounds;
+        if (bounds == null) {
+            destinationBounds = getDefaultBounds(mReentrySnapFraction);
         } else {
-            targetStackBounds = new Rect();
-            targetStackBounds.set(stackBounds);
+            destinationBounds = new Rect(bounds);
         }
         if (isValidPictureInPictureAspectRatio(aspectRatio)) {
-            transformBoundsToAspectRatio(targetStackBounds, aspectRatio,
-                    true /* useCurrentMinEdgeSize */);
+            transformBoundsToAspectRatio(destinationBounds, aspectRatio,
+                    false /* useCurrentMinEdgeSize */);
         }
-        if (targetStackBounds.equals(stackBounds)) {
+        if (destinationBounds.equals(bounds)) {
             return;
         }
         mAspectRatio = aspectRatio;
         onResetReentrySnapFractionUnchecked();
-        // TODO: callback Window Manager on starting animation with calculated bounds
+        try {
+            mPinnedStackController.startAnimation(destinationBounds, sourceRectHint,
+                    -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to start PiP animation from SysUI", e);
+        }
     }
 
     /**
@@ -358,6 +363,7 @@
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
         pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
         pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
         pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 3be3422..8dfae32 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -32,14 +32,16 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Pair;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -58,8 +60,12 @@
     private IActivityTaskManager mActivityTaskManager;
     private Handler mHandler = new Handler();
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
 
+    private PipBoundsHandler mPipBoundsHandler;
     private InputConsumerController mInputConsumerController;
     private PipMenuActivityController mMenuController;
     private PipMediaController mMediaController;
@@ -119,11 +125,11 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onListenerRegistered(IPinnedStackController controller) {
             mHandler.post(() -> {
+                mPipBoundsHandler.setPinnedStackController(controller);
                 mTouchHandler.setPinnedStackController(controller);
             });
         }
@@ -131,6 +137,7 @@
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
                 mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
             });
         }
@@ -138,31 +145,66 @@
         @Override
         public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
             mHandler.post(() -> {
-               mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mPipBoundsHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
             });
         }
 
         @Override
         public void onMinimizedStateChanged(boolean isMinimized) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onMinimizedStateChanged(isMinimized);
                 mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
             });
         }
 
         @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
-                        fromImeAdjustment, fromShelfAdjustment, displayRotation);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+                        mTmpDisplayInfo.rotation);
             });
         }
 
         @Override
         public void onActionsChanged(ParceledListSlice actions) {
+            mHandler.post(() -> mMenuController.setAppActions(actions));
+        }
+
+        @Override
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+            mHandler.post(() -> mPipBoundsHandler.onSaveReentrySnapFraction(componentName, bounds));
+        }
+
+        @Override
+        public void onResetReentrySnapFraction(ComponentName componentName) {
+            mHandler.post(() -> mPipBoundsHandler.onResetReentrySnapFraction(componentName));
+        }
+
+        @Override
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+            mHandler.post(() -> mPipBoundsHandler.onDisplayInfoChanged(displayInfo));
+        }
+
+        @Override
+        public void onConfigurationChanged() {
+            mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged());
+        }
+
+        @Override
+        public void onAspectRatioChanged(float aspectRatio) {
+            mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
+        }
+
+        @Override
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
             mHandler.post(() -> {
-                mMenuController.setAppActions(actions);
+                mPipBoundsHandler.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
             });
         }
     }
@@ -184,12 +226,13 @@
         }
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
         mMediaController = new PipMediaController(context, mActivityManager);
         mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager, mActivityTaskManager,
-                mMenuController, mInputConsumerController);
+                mMenuController, mInputConsumerController, mPipBoundsHandler);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
 
@@ -252,5 +295,6 @@
         mInputConsumerController.dump(pw, innerPrefix);
         mMenuController.dump(pw, innerPrefix);
         mTouchHandler.dump(pw, innerPrefix);
+        mPipBoundsHandler.dump(pw, innerPrefix);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 30cf412..1f36d97 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -48,6 +48,7 @@
 import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.systemui.R;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
@@ -75,6 +76,7 @@
     private final IActivityTaskManager mActivityTaskManager;
     private final ViewConfiguration mViewConfig;
     private final PipMenuListener mMenuListener = new PipMenuListener();
+    private final PipBoundsHandler mPipBoundsHandler;
     private IPinnedStackController mPinnedStackController;
 
     private final PipMenuActivityController mMenuController;
@@ -178,7 +180,8 @@
 
     public PipTouchHandler(Context context, IActivityManager activityManager,
             IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
-            InputConsumerController inputConsumerController) {
+            InputConsumerController inputConsumerController,
+            PipBoundsHandler pipBoundsHandler) {
 
         // Initialize the Pip input consumer
         mContext = context;
@@ -211,6 +214,8 @@
         inputConsumerController.setInputListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
         onRegistrationChanged(inputConsumerController.isRegistered());
+
+        mPipBoundsHandler = pipBoundsHandler;
     }
 
     public void setTouchEnabled(boolean enabled) {
@@ -787,14 +792,8 @@
         mMovementBounds = isMenuExpanded
                 ? mExpandedMovementBounds
                 : mNormalMovementBounds;
-        try {
-            if (mPinnedStackController != null) {
-                mPinnedStackController.setMinEdgeSize(
-                        isMenuExpanded ? mExpandedShortestEdgeSize : 0);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not set minimized state", e);
-        }
+        mPipBoundsHandler.setMinEdgeSize(
+                isMenuExpanded ? mExpandedShortestEdgeSize : 0);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 918af4f..81d6973 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -19,13 +19,10 @@
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -46,16 +43,15 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
+import android.view.DisplayInfo;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -111,9 +107,8 @@
     private int mSuspendPipResizingReason;
 
     private Context mContext;
-    private IActivityManager mActivityManager;
+    private PipBoundsHandler mPipBoundsHandler;
     private IActivityTaskManager mActivityTaskManager;
-    private IWindowManager mWindowManager;
     private MediaSessionManager mMediaSessionManager;
     private int mState = STATE_NO_PIP;
     private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
@@ -135,11 +130,16 @@
     private PipNotification mPipNotification;
     private ParceledListSlice mCustomActions;
 
+    // Used to calculate the movement bounds
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
+
     // Keeps track of the IME visibility to adjust the PiP when the IME is visible
     private boolean mImeVisible;
     private int mImeHeightAdjustment;
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
 
     private final Runnable mResizePinnedStackRunnable = new Runnable() {
         @Override
@@ -181,11 +181,7 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {}
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mState == STATE_PIP) {
@@ -205,17 +201,13 @@
         }
 
         @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {}
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mDefaultPipBounds.set(normalBounds);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mDefaultPipBounds.set(animatingBounds);
             });
         }
 
@@ -241,10 +233,8 @@
         }
         mInitialized = true;
         mContext = context;
-
-        mActivityManager = ActivityManager.getService();
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mActivityTaskManager = ActivityTaskManager.getService();
-        mWindowManager = WindowManagerGlobal.getWindowManagerService();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
@@ -291,7 +281,7 @@
                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
 
         try {
-            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
+            WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 575b559..4cd3ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -18,7 +18,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -36,8 +35,6 @@
 import android.util.MathUtils;
 import android.util.StatsLog;
 import android.view.Gravity;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.ISystemGestureExclusionListener;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -57,6 +54,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -72,35 +70,13 @@
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
             "gestures.back_timeout", 250);
 
-    private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() {
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {
-        }
-
+    private final PinnedStackListener mImeChangedListener = new PinnedStackListener() {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             // No need to thread jump, assignments are atomic
             mImeHeight = imeVisible ? imeHeight : 0;
             // TODO: Probably cancel any existing gesture
         }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
-        }
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
-        }
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) {
-        }
     };
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 50200a7..8bb37bb 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -4940,12 +4940,6 @@
         }
     }
 
-
-    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-        if (getTaskStack() == null) return null;
-        return getTaskStack().getPictureInPictureBounds(aspectRatio, null /* currentStackBounds */);
-    }
-
     void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
             boolean fromFullscreen) {
         if (!inPinnedWindowingMode()) return;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ffd9021..71f02e8 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1021,7 +1021,7 @@
         if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this);
         mAppStopped = true;
         // Reset the last saved PiP snap fraction on app stop.
-        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
+        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
         destroySurfaces();
         // Remove any starting window that was added for this app if they are still around.
         removeStartingWindow();
@@ -1705,10 +1705,7 @@
             return;
         }
 
-        if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) {
-            // Entering PiP from fullscreen, reset the snap fraction
-            mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
-        } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
+        if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
                 && !isHidden()) {
             // Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
             // for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
@@ -1726,8 +1723,8 @@
                     stackBounds = mTmpRect;
                     pinnedStack.getBounds(stackBounds);
                 }
-                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
-                        stackBounds);
+                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(
+                        mActivityComponent, stackBounds);
             }
         } else if (shouldStartChangeTransition(prevWinMode, winMode)) {
             initializeChangeTransition(mTmpPrevBounds);
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index ef0049b..8e57fec 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -50,7 +51,6 @@
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -74,7 +74,7 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
 
-    public static final float INVALID_SNAP_FRACTION = -1f;
+    private static final float INVALID_SNAP_FRACTION = -1f;
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final Handler mHandler = UiThread.getHandler();
@@ -106,9 +106,6 @@
     private int mDefaultStackGravity;
     private float mDefaultAspectRatio;
     private Point mScreenEdgeInsets;
-    private int mCurrentMinSize;
-    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
-    private WeakReference<AppWindowToken> mLastPipActivity = null;
 
     // The aspect ratio bounds of the PIP.
     private float mMinAspectRatio;
@@ -118,7 +115,6 @@
     private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
     private final Rect mTmpInsets = new Rect();
     private final Rect mTmpRect = new Rect();
-    private final Rect mTmpAnimatingBoundsRect = new Rect();
     private final Point mTmpDisplaySize = new Point();
 
 
@@ -136,18 +132,21 @@
         }
 
         @Override
-        public void setMinEdgeSize(int minEdgeSize) {
-            mHandler.post(() -> {
-                mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize);
-            });
-        }
-
-        @Override
         public int getDisplayRotation() {
             synchronized (mService.mGlobalLock) {
                 return mDisplayInfo.rotation;
             }
         }
+
+        @Override
+        public void startAnimation(Rect destinationBounds, Rect sourceRectHint,
+                int animationDuration) {
+            synchronized (mService.mGlobalLock) {
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
+                pinnedStack.animateResizePinnedStack(destinationBounds,
+                        sourceRectHint, animationDuration, true /* fromFullscreen */);
+            }
+        }
     }
 
     /**
@@ -188,7 +187,6 @@
         final Resources res = mService.mContext.getResources();
         mDefaultMinSize = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
-        mCurrentMinSize = mDefaultMinSize;
         mDefaultAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
         final String screenEdgeInsetsDpString = res.getString(
@@ -216,6 +214,7 @@
             listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
             listener.onListenerRegistered(mCallbacks);
             mPinnedStackListener = listener;
+            notifyDisplayInfoChanged(mDisplayInfo);
             notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
             notifyShelfVisibilityChanged(mIsShelfShowing, mShelfHeight);
             // The movement bounds notification needs to be sent before the minimized state, since
@@ -238,58 +237,34 @@
     }
 
     /**
-     * Returns the current bounds (or the default bounds if there are no current bounds) with the
-     * specified aspect ratio.
-     */
-    Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
-            boolean useCurrentMinEdgeSize) {
-        // Save the snap fraction, calculate the aspect ratio based on screen size
-        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
-                getMovementBounds(stackBounds));
-
-        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
-        final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
-                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
-        final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
-        final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
-        stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
-        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
-        if (mIsMinimized) {
-            applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
-        }
-        return stackBounds;
-    }
-
-    /**
      * Saves the current snap fraction for re-entry of the current activity into PiP.
      */
-    void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) {
-        mReentrySnapFraction = getSnapFraction(stackBounds);
-        mLastPipActivity = new WeakReference<>(token);
+    void saveReentrySnapFraction(final ComponentName componentName, final Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onSaveReentrySnapFraction(componentName, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering save reentry fraction event.", e);
+        }
     }
 
     /**
      * Resets the last saved snap fraction so that the default bounds will be returned.
      */
-    void resetReentrySnapFraction(AppWindowToken token) {
-        if (mLastPipActivity != null && mLastPipActivity.get() == token) {
-            mReentrySnapFraction = INVALID_SNAP_FRACTION;
-            mLastPipActivity = null;
+    void resetReentrySnapFraction(ComponentName componentName) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onResetReentrySnapFraction(componentName);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e);
         }
     }
 
     /**
-     * @return the default bounds to show the PIP when there is no active PIP.
-     */
-    Rect getDefaultOrLastSavedBounds() {
-        return getDefaultBounds(mReentrySnapFraction);
-    }
-
-    /**
      * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
      * will apply the default bounds to the provided snap fraction.
      */
-    Rect getDefaultBounds(float snapFraction) {
+    private Rect getDefaultBounds(float snapFraction) {
         synchronized (mService.mGlobalLock) {
             final Rect insetBounds = new Rect();
             getInsetBounds(insetBounds);
@@ -311,13 +286,18 @@
         }
     }
 
+    private void setDisplayInfo(DisplayInfo displayInfo) {
+        mDisplayInfo.copyFrom(displayInfo);
+        notifyDisplayInfoChanged(mDisplayInfo);
+    }
+
     /**
      * In the case where the display rotation is changed but there is no stack, we can't depend on
      * onTaskStackBoundsChanged() to be called.  But we still should update our known display info
      * with the new state so that we can update SystemUI.
      */
     synchronized void onDisplayInfoChanged(DisplayInfo displayInfo) {
-        mDisplayInfo.copyFrom(displayInfo);
+        setDisplayInfo(displayInfo);
         notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
     }
 
@@ -335,7 +315,7 @@
             } else if (targetBounds.isEmpty()) {
                 // The stack is null, we are just initializing the stack, so just store the display
                 // info and ignore
-                mDisplayInfo.copyFrom(displayInfo);
+                setDisplayInfo(displayInfo);
                 outBounds.setEmpty();
                 return false;
             }
@@ -345,7 +325,8 @@
 
             // Calculate the snap fraction of the current stack along the old movement bounds
             final float snapFraction = getSnapFraction(postChangeStackBounds);
-            mDisplayInfo.copyFrom(displayInfo);
+
+            setDisplayInfo(displayInfo);
 
             // Calculate the stack bounds in the new orientation to the same same fraction along the
             // rotated movement bounds.
@@ -406,8 +387,11 @@
     void setAspectRatio(float aspectRatio) {
         if (Float.compare(mAspectRatio, aspectRatio) != 0) {
             mAspectRatio = aspectRatio;
+            notifyAspectRatioChanged(aspectRatio);
             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
                     false /* fromShelfAdjustment */);
+            notifyPrepareAnimation(null /* sourceHintRect */, aspectRatio,
+                    null /* stackBounds */);
         }
     }
 
@@ -429,6 +413,10 @@
         notifyActionsChanged(mActions);
     }
 
+    void prepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        notifyPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+    }
+
     private boolean isSameDimensionAndRotation(@NonNull DisplayInfo display1,
             @NonNull DisplayInfo display2) {
         Preconditions.checkNotNull(display1);
@@ -461,6 +449,15 @@
         }
     }
 
+    private void notifyAspectRatioChanged(float aspectRatio) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onAspectRatioChanged(aspectRatio);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
+        }
+    }
+
     /**
      * Notifies listeners that the PIP minimized state has changed.
      */
@@ -497,23 +494,13 @@
                 return;
             }
             try {
-                final Rect insetBounds = new Rect();
-                getInsetBounds(insetBounds);
-                final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
-                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
-                    transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                            false /* useCurrentMinEdgeSize */);
-                }
-                final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                final Rect animatingBounds = new Rect();
                 final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
                 if (pinnedStack != null) {
                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
-                } else {
-                    animatingBounds.set(normalBounds);
                 }
-                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
-                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
-                        mDisplayInfo.rotation);
+                mPinnedStackListener.onMovementBoundsChanged(animatingBounds,
+                        fromImeAdjustment, fromShelfAdjustment);
             } catch (RemoteException e) {
                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
             }
@@ -521,6 +508,30 @@
     }
 
     /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyDisplayInfoChanged(DisplayInfo displayInfo) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onDisplayInfoChanged(displayInfo);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering DisplayInfo changed event.", e);
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering prepare animation event.", e);
+        }
+    }
+
+    /**
      * @return the bounds on the screen that the PIP can be visible in.
      */
     private void getInsetBounds(Rect outRect) {
@@ -604,7 +615,6 @@
         pw.println(prefix + "  mImeHeight=" + mImeHeight);
         pw.println(prefix + "  mIsShelfShowing=" + mIsShelfShowing);
         pw.println(prefix + "  mShelfHeight=" + mShelfHeight);
-        pw.println(prefix + "  mReentrySnapFraction=" + mReentrySnapFraction);
         pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
         pw.println(prefix + "  mAspectRatio=" + mAspectRatio);
         pw.println(prefix + "  mMinAspectRatio=" + mMinAspectRatio);
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 510a18f..d9e30a2 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -959,10 +959,6 @@
         // Need to make sure the pinned stack exist so we can resize it below...
         stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
-        // Calculate the target bounds here before the task is reparented back into pinned windowing
-        // mode (which will reset the saved bounds)
-        final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
-
         try {
             final TaskRecord task = r.getTaskRecord();
             // Resize the pinned stack to match the current size of the task the activity we are
@@ -1001,9 +997,14 @@
             mService.continueWindowLayout();
         }
 
-        stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
-                true /* fromFullscreen */);
+        // Notify the pinned stack controller to prepare the PiP animation, expect callback
+        // delivered from SystemUI to WM to start the animation.
+        final PinnedStackController pinnedStackController =
+                display.mDisplayContent.getPinnedStackController();
+        pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+                null /* stackBounds */);
 
+        // TODO: revisit the following statement after the animation is moved from WM to SysUI.
         // Update the visibility of all activities after the they have been reparented to the new
         // stack.  This MUST run after the animation above is scheduled to ensure that the windows
         // drawn signal is scheduled after the bounds animation start call on the bounds animator
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 239bd00..58542e5 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1661,40 +1661,6 @@
     }
 
     /**
-     * @return the current stack bounds transformed to the given {@param aspectRatio}. If
-     *         the default bounds is {@code null}, then the {@param aspectRatio} is applied to the
-     *         default bounds.
-     */
-    Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
-        if (!mWmService.mAtmService.mSupportsPictureInPicture) {
-            return null;
-        }
-
-        final DisplayContent displayContent = getDisplayContent();
-        if (displayContent == null) {
-            return null;
-        }
-
-        if (!inPinnedWindowingMode()) {
-            return null;
-        }
-
-        final PinnedStackController pinnedStackController =
-                displayContent.getPinnedStackController();
-        if (stackBounds == null) {
-            // Calculate the aspect ratio bounds from the default bounds
-            stackBounds = pinnedStackController.getDefaultOrLastSavedBounds();
-        }
-
-        if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
-            return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio,
-                    true /* useCurrentMinEdgeSize */);
-        } else {
-            return stackBounds;
-        }
-    }
-
-    /**
      * Animates the pinned stack.
      */
     void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
@@ -1771,6 +1737,11 @@
             return;
         }
 
+        final DisplayContent displayContent = getDisplayContent();
+        if (displayContent == null) {
+            return;
+        }
+
         if (!inPinnedWindowingMode()) {
             return;
         }
@@ -1781,13 +1752,10 @@
         if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) {
             return;
         }
-        getAnimationOrCurrentBounds(mTmpFromBounds);
-        mTmpToBounds.set(mTmpFromBounds);
-        getPictureInPictureBounds(aspectRatio, mTmpToBounds);
-        if (!mTmpToBounds.equals(mTmpFromBounds)) {
-            animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */,
-                    -1 /* duration */, false /* fromFullscreen */);
-        }
+
+        // Notify the pinned stack controller about aspect ratio change.
+        // This would result a callback delivered from SystemUI to WM to start animation,
+        // if the bounds are ought to be altered due to aspect ratio change.
         pinnedStackController.setAspectRatio(
                 pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
                         ? aspectRatio : -1f);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index e6d7632..1731f7c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -38,7 +38,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.testing.DexmakerShareClassLoaderRule;
@@ -433,12 +432,7 @@
             final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor;
             if (mWindowingMode == WINDOWING_MODE_PINNED) {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
-                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) {
-                    @Override
-                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-                        return new Rect(50, 50, 100, 100);
-                    }
-                };
+                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop);
             } else {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
                         mWindowingMode, mActivityType, mOnTop);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
index efd468f..e9c2263 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
@@ -66,8 +66,8 @@
 
         verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(false), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(false));
         verify(mIPinnedStackListener).onActionsChanged(any());
         verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
 
@@ -75,8 +75,8 @@
 
         mWm.setShelfHeight(true, SHELF_HEIGHT);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(true), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(true));
         verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt());
     }
 }