Merge "Adding source bounds hint to support better PiP transition."
diff --git a/api/current.txt b/api/current.txt
index 33eb074..24586f0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5667,6 +5667,7 @@
     method public int describeContents();
     method public void setActions(java.util.List<android.app.RemoteAction>);
     method public void setAspectRatio(float);
+    method public void setSourceRectHint(android.graphics.Rect);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 9328420..7e92671 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5859,6 +5859,7 @@
     method public int describeContents();
     method public void setActions(java.util.List<android.app.RemoteAction>);
     method public void setAspectRatio(float);
+    method public void setSourceRectHint(android.graphics.Rect);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 39663f2..d33c1fa 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5678,6 +5678,7 @@
     method public int describeContents();
     method public void setActions(java.util.List<android.app.RemoteAction>);
     method public void setAspectRatio(float);
+    method public void setSourceRectHint(android.graphics.Rect);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
   }
diff --git a/core/java/android/app/PictureInPictureArgs.java b/core/java/android/app/PictureInPictureArgs.java
index fbdcbf4..0ce5eeb 100644
--- a/core/java/android/app/PictureInPictureArgs.java
+++ b/core/java/android/app/PictureInPictureArgs.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.Nullable;
+import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,11 +36,19 @@
     private Float mAspectRatio;
 
     /**
-     * The set of actions that are associated with this activity when in picture in picture.
+     * The set of actions that are associated with this activity when in picture-in-picture.
      */
     @Nullable
     private List<RemoteAction> mUserActions;
 
+    /**
+     * The source bounds hint used when entering picture-in-picture, relative to the window bounds.
+     * We can use this internally for the transition into picture-in-picture to ensure that a
+     * particular source rect is visible throughout the whole transition.
+     */
+    @Nullable
+    private Rect mSourceRectHint;
+
     PictureInPictureArgs(Parcel in) {
         if (in.readInt() != 0) {
             mAspectRatio = in.readFloat();
@@ -48,6 +57,9 @@
             mUserActions = new ArrayList<>();
             in.readParcelableList(mUserActions, RemoteAction.class.getClassLoader());
         }
+        if (in.readInt() != 0) {
+            mSourceRectHint = Rect.CREATOR.createFromParcel(in);
+        }
     }
 
     /**
@@ -79,6 +91,9 @@
         if (otherArgs.hasSetActions()) {
             mUserActions = otherArgs.mUserActions;
         }
+        if (otherArgs.hasSourceBoundsHint()) {
+            mSourceRectHint = new Rect(otherArgs.getSourceRectHint());
+        }
     }
 
     /**
@@ -137,9 +152,43 @@
         return mUserActions != null;
     }
 
+    /**
+     * Sets the source bounds hint. These bounds are only used when an activity first enters
+     * picture-in-picture, and describe the bounds in window coordinates of activity entering
+     * picture-in-picture that will be visible following the transition. For the best effect, these
+     * bounds should also match the aspect ratio in the arguments.
+     */
+    public void setSourceRectHint(Rect launchBounds) {
+        if (launchBounds == null) {
+            mSourceRectHint = null;
+        } else {
+            mSourceRectHint = new Rect(launchBounds);
+        }
+    }
+
+    /**
+     * @return the launch bounds
+     * @hide
+     */
+    public Rect getSourceRectHint() {
+        return mSourceRectHint;
+    }
+
+    /**
+     * @return whether there are launch bounds set
+     * @hide
+     */
+    public boolean hasSourceBoundsHint() {
+        return mSourceRectHint != null && !mSourceRectHint.isEmpty();
+    }
+
     @Override
     public PictureInPictureArgs clone() {
-        return new PictureInPictureArgs(mAspectRatio, mUserActions);
+        PictureInPictureArgs args = new PictureInPictureArgs(mAspectRatio, mUserActions);
+        if (mSourceRectHint != null) {
+            args.setSourceRectHint(mSourceRectHint);
+        }
+        return args;
     }
 
     @Override
@@ -161,6 +210,12 @@
         } else {
             out.writeInt(0);
         }
+        if (mSourceRectHint != null) {
+            out.writeInt(1);
+            mSourceRectHint.writeToParcel(out, 0);
+        } else {
+            out.writeInt(0);
+        }
     }
 
     public static final Creator<PictureInPictureArgs> CREATOR =
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2be5e77..92a4b78 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7870,10 +7870,11 @@
                     r.pictureInPictureArgs.copyOnlySet(args);
                     final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
                     final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
-                    final Rect bounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
+                    final Rect sourceBounds = r.pictureInPictureArgs.getSourceRectHint();
+                    final Rect destBounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
                             aspectRatio);
-                    mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
-                            bounds, true /* moveHomeStackToFront */);
+                    mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, destBounds,
+                            true /* moveHomeStackToFront */, "enterPictureInPictureMode");
                     final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
                     stack.setPictureInPictureAspectRatio(aspectRatio);
                     stack.setPictureInPictureActions(actions);
@@ -10525,7 +10526,7 @@
     }
 
     @Override
-    public void resizeStack(int stackId, Rect bounds, boolean allowResizeInDockedMode,
+    public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
             boolean preserveWindows, boolean animate, int animationDuration) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
         long ident = Binder.clearCallingIdentity();
@@ -10535,13 +10536,14 @@
                     if (stackId == PINNED_STACK_ID) {
                         final PinnedActivityStack pinnedStack =
                                 mStackSupervisor.getStack(PINNED_STACK_ID);
-                        pinnedStack.animateResizePinnedStack(bounds, animationDuration);
+                        pinnedStack.animateResizePinnedStack(null /* sourceBounds */, destBounds,
+                                animationDuration);
                     } else {
                         throw new IllegalArgumentException("Stack: " + stackId
                                 + " doesn't support animated resize.");
                     }
                 } else {
-                    mStackSupervisor.resizeStackLocked(stackId, bounds, null /* tempTaskBounds */,
+                    mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */,
                             null /* tempTaskInsetBounds */, preserveWindows,
                             allowResizeInDockedMode, !DEFER_RESUME);
                 }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5c49dfd..9c19c2b 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2794,7 +2794,7 @@
         }
     }
 
-    boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) {
+    boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect destBounds) {
         final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
         if (stack == null) {
             throw new IllegalArgumentException(
@@ -2815,13 +2815,14 @@
             return false;
         }
 
-        moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds,
-                true /* moveHomeStackToFront */);
+        moveActivityToPinnedStackLocked(r, null /* sourceBounds */, destBounds,
+                true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack");
         return true;
     }
 
-    void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
-            boolean moveHomeStackToFront) {
+    void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, Rect destBounds,
+            boolean moveHomeStackToFront, String reason) {
+
         mWindowManager.deferSurfaceLayout();
 
         // Need to make sure the pinned stack exist so we can resize it below...
@@ -2889,7 +2890,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        stack.animateResizePinnedStack(bounds, -1 /* animationDuration */);
+        stack.animateResizePinnedStack(sourceBounds, destBounds, -1 /* animationDuration */);
         mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName);
     }
 
diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java
index 1708fe5..32d3082 100644
--- a/services/core/java/com/android/server/am/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/am/PinnedActivityStack.java
@@ -41,8 +41,9 @@
         return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds);
     }
 
-    void animateResizePinnedStack(Rect bounds, int animationDuration) {
-        getWindowContainerController().animateResizePinnedStack(bounds, animationDuration);
+    void animateResizePinnedStack(Rect sourceBounds, Rect destBounds, int animationDuration) {
+        getWindowContainerController().animateResizePinnedStack(sourceBounds, destBounds,
+                animationDuration);
     }
 
     void setPictureInPictureAspectRatio(float aspectRatio) {
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index cd0e6cc..62414e5 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -121,8 +121,8 @@
         private final int mFrozenTaskWidth;
         private final int mFrozenTaskHeight;
 
-        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
-                boolean moveToFullScreen, boolean replacement) {
+        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen,
+                boolean replacement) {
             super();
             mTarget = target;
             mFrom.set(from);
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index 34ccf87..0145454 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -40,29 +40,30 @@
     /**
      * Animates the pinned stack.
      */
-    public void animateResizePinnedStack(Rect bounds, int animationDuration) {
+    public void animateResizePinnedStack(Rect sourceBounds, Rect destBounds,
+            int animationDuration) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
                 throw new IllegalArgumentException("Pinned stack container not found :(");
             }
 
             // Get non-null fullscreen bounds if the bounds are null
-            final boolean moveToFullscreen = bounds == null;
-            bounds = getPinnedStackAnimationBounds(bounds);
+            final boolean moveToFullscreen = destBounds == null;
+            destBounds = getPinnedStackAnimationBounds(destBounds);
 
             // If the bounds are truly null, then there was no fullscreen stack at this time, so
             // animate this to the full display bounds
             final Rect toBounds;
-            if (bounds == null) {
+            if (destBounds == null) {
                 toBounds = new Rect();
                 mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
             } else {
-                toBounds = bounds;
+                toBounds = destBounds;
             }
 
             final Rect originalBounds = new Rect();
             mContainer.getBounds(originalBounds);
-            mContainer.setAnimatingBounds(toBounds);
+            mContainer.setAnimatingBounds(sourceBounds, toBounds);
             UiThread.getHandler().post(() -> {
                 if (mContainer == null) {
                     return;
@@ -82,13 +83,17 @@
                 return;
             }
 
+            final int displayId = mContainer.getDisplayContent().getDisplayId();
+            final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
+            final Rect targetBounds = new Rect();
+            mContainer.getAnimatingBounds(targetBounds);
             final PinnedStackController pinnedStackController =
                     mContainer.getDisplayContent().getPinnedStackController();
 
             if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
-                final int displayId = mContainer.getDisplayContent().getDisplayId();
-                final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
-                animateResizePinnedStack(toBounds, -1 /* duration */);
+                if (!toBounds.equals(targetBounds)) {
+                    animateResizePinnedStack(null /* sourceBounds */, toBounds, -1 /* duration */);
+                }
                 pinnedStackController.setAspectRatio(
                         pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
                                 ? aspectRatio : -1f);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 442cd54..dc437ea 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -128,6 +127,7 @@
     private boolean mBoundsAnimating = false;
     private boolean mBoundsAnimatingToFullscreen = false;
     private Rect mBoundsAnimationTarget = new Rect();
+    private Rect mBoundsAnimationSourceBounds = new Rect();
 
     // Temporary storage for the new bounds that should be used after the configuration change.
     // Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration().
@@ -323,15 +323,31 @@
      * Sets the bounds animation target bounds.  This can't currently be done in onAnimationStart()
      * since that is started on the UiThread.
      */
-    void setAnimatingBounds(Rect bounds) {
-        if (bounds != null) {
-            mBoundsAnimationTarget.set(bounds);
+    void setAnimatingBounds(Rect sourceBounds, Rect destBounds) {
+        if (sourceBounds != null) {
+            mBoundsAnimationSourceBounds.set(sourceBounds);
+        } else {
+            mBoundsAnimationSourceBounds.setEmpty();
+        }
+        if (destBounds != null) {
+            mBoundsAnimationTarget.set(destBounds);
         } else {
             mBoundsAnimationTarget.setEmpty();
         }
     }
 
     /**
+     * @return the source bounds for the bounds animation.
+     */
+    void getAnimatingSourceBounds(Rect outBounds) {
+        if (mBoundsAnimationSourceBounds != null) {
+            outBounds.set(mBoundsAnimationSourceBounds);
+            return;
+        }
+        outBounds.setEmpty();
+    }
+
+    /**
      * @return the bounds that the task stack is currently being animated towards, or the current
      *         stack bounds if there is no animation in progress.
      */
@@ -1465,7 +1481,6 @@
     public void onAnimationEnd() {
         synchronized (mService.mWindowMap) {
             mBoundsAnimating = false;
-            mBoundsAnimationTarget.setEmpty();
             mService.requestTraversal();
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 48de7e4..826fb45 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -67,8 +67,6 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Transformation;
 
-import com.android.server.wm.WindowManagerService.H;
-
 import java.io.PrintWriter;
 import java.io.FileDescriptor;
 
@@ -156,6 +154,8 @@
     Rect mLastClipRect = new Rect();
     Rect mLastFinalClipRect = new Rect();
     Rect mTmpStackBounds = new Rect();
+    private Rect mTmpAnimatingBounds = new Rect();
+    private Rect mTmpSourceBounds = new Rect();
 
     /**
      * This is rectangle of the window's surface that is not covered by
@@ -1282,6 +1282,7 @@
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
         final WindowState w = mWin;
+        final LayoutParams attrs = mWin.getAttrs();
         final Task task = w.getTask();
 
         // We got resized, so block all updates until we got the new surface.
@@ -1290,7 +1291,7 @@
         }
 
         mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
-        calculateSurfaceBounds(w, w.getAttrs());
+        calculateSurfaceBounds(w, attrs);
 
         mExtraHScale = (float) 1.0;
         mExtraVScale = (float) 1.0;
@@ -1329,23 +1330,59 @@
         float surfaceHeight = mSurfaceController.getHeight();
 
         if (isForceScaled()) {
-            int hInsets = w.getAttrs().surfaceInsets.left + w.getAttrs().surfaceInsets.right;
-            int vInsets = w.getAttrs().surfaceInsets.top + w.getAttrs().surfaceInsets.bottom;
+            int hInsets = attrs.surfaceInsets.left + attrs.surfaceInsets.right;
+            int vInsets = attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
+            float surfaceContentWidth = surfaceWidth - hInsets;
+            float surfaceContentHeight = surfaceHeight - vInsets;
             if (!mForceScaleUntilResize) {
                 mSurfaceController.forceScaleableInTransaction(true);
             }
 
+            int posX = mTmpSize.left;
+            int posY = mTmpSize.top;
             task.mStack.getDimBounds(mTmpStackBounds);
-            // We want to calculate the scaling based on the content area, not based on
-            // the entire surface, so that we scale in sync with windows that don't have insets.
-            mExtraHScale = mTmpStackBounds.width() / (float)(surfaceWidth - hInsets);
-            mExtraVScale = mTmpStackBounds.height() / (float)(surfaceHeight - vInsets);
+            task.mStack.getAnimatingSourceBounds(mTmpSourceBounds);
+            if (!mTmpSourceBounds.isEmpty()) {
+                // Get the final target stack bounds, if we are not animating, this is just the
+                // current stack bounds
+                task.mStack.getAnimatingBounds(mTmpAnimatingBounds);
+
+                // Calculate the current progress and interpolate the difference between the target
+                // and source bounds
+                float finalWidth = mTmpAnimatingBounds.width();
+                float initialWidth = mTmpSourceBounds.width();
+                float t = (surfaceContentWidth - mTmpStackBounds.width())
+                        / (surfaceContentWidth - mTmpAnimatingBounds.width());
+                mExtraHScale = (initialWidth + t * (finalWidth - initialWidth)) / initialWidth;
+                mExtraVScale = mExtraHScale;
+
+                // Adjust the position to account for the inset bounds
+                posX -= (int) (t * mExtraHScale * mTmpSourceBounds.left);
+                posY -= (int) (t * mExtraVScale * mTmpSourceBounds.top);
+
+                // Always clip to the stack bounds since the surface can be larger with the current
+                // scale
+                clipRect = null;
+                finalClipRect = mTmpStackBounds;
+            } else {
+                // We want to calculate the scaling based on the content area, not based on
+                // the entire surface, so that we scale in sync with windows that don't have insets.
+                mExtraHScale = mTmpStackBounds.width() / surfaceContentWidth;
+                mExtraVScale = mTmpStackBounds.height() / surfaceContentHeight;
+
+                // Since we are scaled to fit in our previously desired crop, we can now
+                // expose the whole window in buffer space, and not risk extending
+                // past where the system would have cropped us
+                clipRect = null;
+                finalClipRect = null;
+            }
 
             // In the case of ForceScaleToStack we scale entire tasks together,
             // and so we need to scale our offsets relative to the task bounds
             // or parent and child windows would fall out of alignment.
-            int posX = (int) (mTmpSize.left - w.mAttrs.x * (1 - mExtraHScale));
-            int posY = (int) (mTmpSize.top - w.mAttrs.y * (1 - mExtraVScale));
+            posX -= (int) (attrs.x * (1 - mExtraHScale));
+            posY -= (int) (attrs.y * (1 - mExtraVScale));
+
             // Imagine we are scaling down. As we scale the buffer down, we decrease the
             // distance between the surface top left, and the start of the surface contents
             // (previously it was surfaceInsets.left pixels in screen space but now it
@@ -1353,17 +1390,11 @@
             // non inset content at the same position, we have to shift the whole window
             // forward. Likewise for scaling up, we've increased this distance, and we need
             // to shift by a negative number to compensate.
-            posX += w.getAttrs().surfaceInsets.left * (1 - mExtraHScale);
-            posY += w.getAttrs().surfaceInsets.top * (1 - mExtraVScale);
+            posX += attrs.surfaceInsets.left * (1 - mExtraHScale);
+            posY += attrs.surfaceInsets.top * (1 - mExtraVScale);
 
-            mSurfaceController.setPositionInTransaction((float)Math.floor(posX),
-                    (float)Math.floor(posY), recoveringMemory);
-
-            // Since we are scaled to fit in our previously desired crop, we can now
-            // expose the whole window in buffer space, and not risk extending
-            // past where the system would have cropped us
-            clipRect = null;
-            finalClipRect = null;
+            mSurfaceController.setPositionInTransaction((float) Math.floor(posX),
+                    (float) Math.floor(posY), recoveringMemory);
 
             // Various surfaces in the scaled stack may resize at different times.
             // We need to ensure for each surface, that we disable transformation matrix