Add transition when swiping from fullscreen to enter PiP

There are some artifacts actually come from test app implementation
- when in landscape fullscreen, sourceRectHint would include the black
  background on both sides
- when settled in PiP, app does the transition which causes the final
  glitch
Would address these later in the test app itself

Video: http://rcll/aaaaaabFQoRHlzixHdtY/cOco1yoMXwFzSifFqQET9U
Video: http://rcll/aaaaaabFQoRHlzixHdtY/f3IWG09DRO9aUNnzqSdPLU
Bug: 171802909
Test: see video
Change-Id: I783e51e78277b1179a7e8de50050b7c9e1a6de17
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 201dfe7..f82bc2d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -15,6 +15,9 @@
  */
 package com.android.quickstep;
 
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
@@ -1065,7 +1068,7 @@
                             runningTaskTarget.pictureInPictureParams) != null;
             if (mIsSwipingPipToHome) {
                 mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
-                        homeAnimFactory, runningTaskTarget);
+                        homeAnimFactory, runningTaskTarget, start);
                 mSwipePipToHomeAnimator.setDuration(SWIPE_PIP_TO_HOME_DURATION);
                 mSwipePipToHomeAnimator.setInterpolator(interpolator);
                 mSwipePipToHomeAnimator.setFloatValues(0f, 1f);
@@ -1135,23 +1138,34 @@
     }
 
     private SwipePipToHomeAnimator getSwipePipToHomeAnimator(HomeAnimationFactory homeAnimFactory,
-            RemoteAnimationTargetCompat runningTaskTarget) {
+            RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
         final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
         final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
+        final int windowRotation = orientationState.getDisplayRotation();
+        final int homeRotation = orientationState.getRecentsActivityRotation();
         final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
                 .startSwipePipToHome(taskInfo.topActivity,
                         TaskInfoCompat.getTopActivityInfo(taskInfo),
                         runningTaskTarget.pictureInPictureParams,
-                        orientationState.getRecentsActivityRotation(),
+                        homeRotation,
                         mDp.hotseatBarSizePx);
+        final Rect startBounds = new Rect();
+        updateProgressForStartRect(new Matrix(), startProgress).round(startBounds);
         final SwipePipToHomeAnimator swipePipToHomeAnimator = new SwipePipToHomeAnimator(
                 runningTaskTarget.taskId,
                 taskInfo.topActivity,
                 runningTaskTarget.leash.getSurfaceControl(),
                 TaskInfoCompat.getPipSourceRectHint(runningTaskTarget.pictureInPictureParams),
                 TaskInfoCompat.getWindowConfigurationBounds(taskInfo),
+                startBounds,
                 destinationBounds);
+        // We would assume home and app window always in the same rotation While homeRotation
+        // is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
+        if (homeRotation == ROTATION_0
+                && (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
+            swipePipToHomeAnimator.setFromRotation(mTaskViewSimulator, windowRotation);
+        }
         swipePipToHomeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index f696efe..c0087b0 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -175,6 +175,24 @@
     }
 
     /**
+     * Update with start progress for window animation to home.
+     * @param outMatrix {@link Matrix} to map a rect in Launcher space to window space.
+     * @param startProgress The progress of {@link #mCurrentShift} to start thw window from.
+     * @return {@link RectF} represents the bounds as starting point in window space.
+     */
+    protected RectF updateProgressForStartRect(Matrix outMatrix, float startProgress) {
+        mCurrentShift.updateValue(startProgress);
+        mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
+        RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+
+        mTaskViewSimulator.applyWindowToHomeRotation(outMatrix);
+
+        final RectF startRect = new RectF(cropRectF);
+        mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
+        return startRect;
+    }
+
+    /**
      * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
      * @param homeAnimationFactory The home animation factory.
@@ -183,16 +201,11 @@
             HomeAnimationFactory homeAnimationFactory) {
         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
 
-        mCurrentShift.updateValue(startProgress);
-        mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
+        Matrix homeToWindowPositionMap = new Matrix();
+        final RectF startRect = updateProgressForStartRect(
+                homeToWindowPositionMap, startProgress);
         RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
 
-        // Matrix to map a rect in Launcher space to window space
-        Matrix homeToWindowPositionMap = new Matrix();
-        mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap);
-
-        final RectF startRect = new RectF(cropRectF);
-        mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
         // Move the startRect to Launcher space as floatingIconView runs in Launcher
         Matrix windowToHomePositionMap = new Matrix();
         homeToWindowPositionMap.invert(windowToHomePositionMap);
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 16ca43d..8fbd645 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -22,7 +22,11 @@
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.Surface;
 import android.view.SurfaceControl;
 
 import androidx.annotation.NonNull;
@@ -42,9 +46,12 @@
  */
 public class SwipePipToHomeAnimator extends ValueAnimator implements
         ValueAnimator.AnimatorUpdateListener {
+    private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName();
+
     private final int mTaskId;
     private final ComponentName mComponentName;
     private final SurfaceControl mLeash;
+    private final Rect mAppBounds = new Rect();
     private final Rect mStartBounds = new Rect();
     private final Rect mDestinationBounds = new Rect();
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
@@ -55,29 +62,50 @@
     private final Rect mSourceHintRectInsets = new Rect();
     private final Rect mSourceInsets = new Rect();
 
+    /** for rotation via {@link #setFromRotation(TaskViewSimulator, int)} */
+    private @RecentsOrientedState.SurfaceRotation int mFromRotation = Surface.ROTATION_0;
+    private final Rect mDestinationBoundsTransformed = new Rect();
+    private final Rect mDestinationBoundsAnimation = new Rect();
+
     /**
      * Flag to avoid the double-end problem since the leash would have been released
      * after the first end call and any further operations upon it would lead to NPE.
      */
     private boolean mHasAnimationEnded;
 
+    /**
+     * @param taskId Task id associated with this animator, see also {@link #getTaskId()}
+     * @param componentName Component associated with this animator,
+     *                      see also {@link #getComponentName()}
+     * @param leash {@link SurfaceControl} this animator operates on
+     * @param sourceRectHint See the definition in {@link android.app.PictureInPictureParams}
+     * @param appBounds Bounds of the application, sourceRectHint is based on this bounds
+     * @param startBounds Bounds of the application when this animator starts. This can be
+     *                    different from the appBounds if user has swiped a certain distance and
+     *                    Launcher has performed transform on the leash.
+     * @param destinationBounds Bounds of the destination this animator ends to
+     */
     public SwipePipToHomeAnimator(int taskId,
             @NonNull ComponentName componentName,
             @NonNull SurfaceControl leash,
             @NonNull Rect sourceRectHint,
+            @NonNull Rect appBounds,
             @NonNull Rect startBounds,
             @NonNull Rect destinationBounds) {
         mTaskId = taskId;
         mComponentName = componentName;
         mLeash = leash;
+        mAppBounds.set(appBounds);
         mStartBounds.set(startBounds);
         mDestinationBounds.set(destinationBounds);
+        mDestinationBoundsTransformed.set(mDestinationBounds);
+        mDestinationBoundsAnimation.set(mDestinationBounds);
         mSurfaceTransactionHelper = new PipSurfaceTransactionHelper();
 
-        mSourceHintRectInsets.set(sourceRectHint.left - startBounds.left,
-                sourceRectHint.top - startBounds.top,
-                startBounds.right - sourceRectHint.right,
-                startBounds.bottom - sourceRectHint.bottom);
+        mSourceHintRectInsets.set(sourceRectHint.left - appBounds.left,
+                sourceRectHint.top - appBounds.top,
+                appBounds.right - sourceRectHint.right,
+                appBounds.bottom - sourceRectHint.bottom);
 
         addListener(new AnimationSuccessListener() {
             @Override
@@ -106,16 +134,62 @@
         addUpdateListener(this);
     }
 
+    /** sets the from rotation if it's different from the target rotation. */
+    public void setFromRotation(TaskViewSimulator taskViewSimulator,
+            @RecentsOrientedState.SurfaceRotation int fromRotation) {
+        if (fromRotation != Surface.ROTATION_90 && fromRotation != Surface.ROTATION_270) {
+            Log.wtf(TAG, "Not a supported rotation, rotation=" + fromRotation);
+            return;
+        }
+        mFromRotation = fromRotation;
+        final Matrix matrix = new Matrix();
+        taskViewSimulator.applyWindowToHomeRotation(matrix);
+
+        // map the destination bounds into window space. mDestinationBounds is always calculated
+        // in the final home space and the animation runs in original window space.
+        final RectF transformed = new RectF(mDestinationBounds);
+        matrix.mapRect(transformed, new RectF(mDestinationBounds));
+        transformed.round(mDestinationBoundsTransformed);
+
+        // set the animation destination bounds for RectEvaluator calculation.
+        // bounds and insets are calculated as if the transition is from mAppBounds to
+        // mDestinationBoundsAnimation, separated from rotate / scale / position.
+        mDestinationBoundsAnimation.set(mAppBounds.left, mAppBounds.top,
+                mAppBounds.left + mDestinationBounds.width(),
+                mAppBounds.top + mDestinationBounds.height());
+    }
+
     @Override
     public void onAnimationUpdate(ValueAnimator animator) {
         if (mHasAnimationEnded) return;
 
         final float fraction = animator.getAnimatedFraction();
-        final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds, mDestinationBounds);
+        final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds,
+                mDestinationBoundsAnimation);
         final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
                 mSourceHintRectInsets);
-        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-        mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mStartBounds, bounds, insets);
+        final SurfaceControl.Transaction tx =
+                PipSurfaceTransactionHelper.newSurfaceControlTransaction();
+        if (mFromRotation == Surface.ROTATION_90 || mFromRotation == Surface.ROTATION_270) {
+            final float degree, positionX, positionY;
+            if (mFromRotation == Surface.ROTATION_90) {
+                degree = -90 * fraction;
+                positionX = fraction * (mDestinationBoundsTransformed.left - mAppBounds.left)
+                        + mAppBounds.left;
+                positionY = fraction * (mDestinationBoundsTransformed.bottom - mAppBounds.top)
+                        + mAppBounds.top;
+            } else {
+                degree = 90 * fraction;
+                positionX = fraction * (mDestinationBoundsTransformed.right - mAppBounds.left)
+                        + mAppBounds.left;
+                positionY = fraction * (mDestinationBoundsTransformed.top - mAppBounds.top)
+                        + mAppBounds.top;
+            }
+            mSurfaceTransactionHelper.scaleAndRotate(tx, mLeash, mAppBounds, bounds, insets,
+                    degree, positionX, positionY);
+        } else {
+            mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mAppBounds, bounds, insets);
+        }
         mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
         tx.apply();
     }
@@ -135,8 +209,9 @@
     private void onAnimationEnd() {
         if (mHasAnimationEnded) return;
 
-        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-        mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBounds);
+        final SurfaceControl.Transaction tx =
+                PipSurfaceTransactionHelper.newSurfaceControlTransaction();
+        mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBoundsTransformed, mFromRotation);
         tx.apply();
         mHasAnimationEnded = true;
     }