1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.pip;
18 
19 import static android.util.RotationUtils.rotateBounds;
20 import static android.view.Surface.ROTATION_270;
21 import static android.view.Surface.ROTATION_90;
22 
23 import android.animation.AnimationHandler;
24 import android.animation.Animator;
25 import android.animation.RectEvaluator;
26 import android.animation.ValueAnimator;
27 import android.annotation.IntDef;
28 import android.app.TaskInfo;
29 import android.content.Context;
30 import android.content.res.TypedArray;
31 import android.graphics.Color;
32 import android.graphics.Rect;
33 import android.view.Choreographer;
34 import android.view.Surface;
35 import android.view.SurfaceControl;
36 import android.view.SurfaceSession;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
40 import com.android.wm.shell.animation.Interpolators;
41 import com.android.wm.shell.transition.Transitions;
42 
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.util.Objects;
46 
47 /**
48  * Controller class of PiP animations (both from and to PiP mode).
49  */
50 public class PipAnimationController {
51     static final float FRACTION_START = 0f;
52     private static final float FRACTION_END = 1f;
53 
54     public static final int ANIM_TYPE_BOUNDS = 0;
55     public static final int ANIM_TYPE_ALPHA = 1;
56 
57     @IntDef(prefix = { "ANIM_TYPE_" }, value = {
58             ANIM_TYPE_BOUNDS,
59             ANIM_TYPE_ALPHA
60     })
61     @Retention(RetentionPolicy.SOURCE)
62     public @interface AnimationType {}
63 
64     public static final int TRANSITION_DIRECTION_NONE = 0;
65     public static final int TRANSITION_DIRECTION_SAME = 1;
66     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
67     public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3;
68     public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4;
69     public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
70     public static final int TRANSITION_DIRECTION_SNAP_AFTER_RESIZE = 6;
71     public static final int TRANSITION_DIRECTION_USER_RESIZE = 7;
72     public static final int TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND = 8;
73 
74     @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
75             TRANSITION_DIRECTION_NONE,
76             TRANSITION_DIRECTION_SAME,
77             TRANSITION_DIRECTION_TO_PIP,
78             TRANSITION_DIRECTION_LEAVE_PIP,
79             TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN,
80             TRANSITION_DIRECTION_REMOVE_STACK,
81             TRANSITION_DIRECTION_SNAP_AFTER_RESIZE,
82             TRANSITION_DIRECTION_USER_RESIZE,
83             TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND
84     })
85     @Retention(RetentionPolicy.SOURCE)
86     public @interface TransitionDirection {}
87 
isInPipDirection(@ransitionDirection int direction)88     public static boolean isInPipDirection(@TransitionDirection int direction) {
89         return direction == TRANSITION_DIRECTION_TO_PIP;
90     }
91 
isOutPipDirection(@ransitionDirection int direction)92     public static boolean isOutPipDirection(@TransitionDirection int direction) {
93         return direction == TRANSITION_DIRECTION_LEAVE_PIP
94                 || direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
95     }
96 
97     /** Whether the given direction represents removing PIP. */
isRemovePipDirection(@ransitionDirection int direction)98     public static boolean isRemovePipDirection(@TransitionDirection int direction) {
99         return direction == TRANSITION_DIRECTION_REMOVE_STACK;
100     }
101 
102     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
103 
104     private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
105             ThreadLocal.withInitial(() -> {
106                 AnimationHandler handler = new AnimationHandler();
107                 handler.setProvider(new SfVsyncFrameCallbackProvider());
108                 return handler;
109             });
110 
111     private PipTransitionAnimator mCurrentAnimator;
112 
PipAnimationController(PipSurfaceTransactionHelper helper)113     public PipAnimationController(PipSurfaceTransactionHelper helper) {
114         mSurfaceTransactionHelper = helper;
115     }
116 
117     @SuppressWarnings("unchecked")
118     @VisibleForTesting
getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float alphaStart, float alphaEnd)119     public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
120             Rect destinationBounds, float alphaStart, float alphaEnd) {
121         if (mCurrentAnimator == null) {
122             mCurrentAnimator = setupPipTransitionAnimator(
123                     PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
124                             alphaEnd));
125         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
126                 && Objects.equals(destinationBounds, mCurrentAnimator.getDestinationBounds())
127                 && mCurrentAnimator.isRunning()) {
128             mCurrentAnimator.updateEndValue(alphaEnd);
129         } else {
130             mCurrentAnimator.cancel();
131             mCurrentAnimator = setupPipTransitionAnimator(
132                     PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
133                             alphaEnd));
134         }
135         return mCurrentAnimator;
136     }
137 
138     @SuppressWarnings("unchecked")
139     /**
140      * Construct and return an animator that animates from the {@param startBounds} to the
141      * {@param endBounds} with the given {@param direction}. If {@param direction} is type
142      * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate
143      * in a better, more smooth manner. If the original bound was rotated and a reset needs to
144      * happen, pass in {@param startingAngle}.
145      *
146      * In the case where one wants to start animation during an intermediate animation (for example,
147      * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate
148      * to the correct snap fraction region), then provide the base bounds, which is current PiP
149      * leash bounds before transformation/any animation. This is so when we try to construct
150      * the different transformation matrices for the animation, we are constructing this based off
151      * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
152      *
153      * If non-zero {@param rotationDelta} is given, it means that the display will be rotated by
154      * leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the
155      * rotation change.
156      */
157     @VisibleForTesting
getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)158     public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
159             Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
160             @PipAnimationController.TransitionDirection int direction, float startingAngle,
161             @Surface.Rotation int rotationDelta) {
162         if (mCurrentAnimator == null) {
163             mCurrentAnimator = setupPipTransitionAnimator(
164                     PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
165                             endBounds, sourceHintRect, direction, 0 /* startingAngle */,
166                             rotationDelta));
167         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
168                 && mCurrentAnimator.isRunning()) {
169             // If we are still animating the fade into pip, then just move the surface and ensure
170             // we update with the new destination bounds, but don't interrupt the existing animation
171             // with a new bounds
172             mCurrentAnimator.setDestinationBounds(endBounds);
173         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS
174                 && mCurrentAnimator.isRunning()) {
175             mCurrentAnimator.setDestinationBounds(endBounds);
176             // construct new Rect instances in case they are recycled
177             mCurrentAnimator.updateEndValue(new Rect(endBounds));
178         } else {
179             mCurrentAnimator.cancel();
180             mCurrentAnimator = setupPipTransitionAnimator(
181                     PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
182                             endBounds, sourceHintRect, direction, startingAngle, rotationDelta));
183         }
184         return mCurrentAnimator;
185     }
186 
getCurrentAnimator()187     PipTransitionAnimator getCurrentAnimator() {
188         return mCurrentAnimator;
189     }
190 
setupPipTransitionAnimator(PipTransitionAnimator animator)191     private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
192         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
193         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
194         animator.setFloatValues(FRACTION_START, FRACTION_END);
195         animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
196         return animator;
197     }
198 
199     /**
200      * Additional callback interface for PiP animation
201      */
202     public static class PipAnimationCallback {
203         /**
204          * Called when PiP animation is started.
205          */
onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator)206         public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {}
207 
208         /**
209          * Called when PiP animation is ended.
210          */
onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, PipTransitionAnimator animator)211         public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
212                 PipTransitionAnimator animator) {}
213 
214         /**
215          * Called when PiP animation is cancelled.
216          */
onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator)217         public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {}
218     }
219 
220     /**
221      * A handler class that could register itself to apply the transaction instead of the
222      * animation controller doing it. For example, the menu controller can be one such handler.
223      */
224     public static class PipTransactionHandler {
225 
226         /**
227          * Called when the animation controller is about to apply a transaction. Allow a registered
228          * handler to apply the transaction instead.
229          *
230          * @return true if handled by the handler, false otherwise.
231          */
handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)232         public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
233                 Rect destinationBounds) {
234             return false;
235         }
236     }
237 
238     /**
239      * Animator for PiP transition animation which supports both alpha and bounds animation.
240      * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
241      */
242     public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
243             ValueAnimator.AnimatorUpdateListener,
244             ValueAnimator.AnimatorListener {
245         private final TaskInfo mTaskInfo;
246         private final SurfaceControl mLeash;
247         private final @AnimationType int mAnimationType;
248         private final Rect mDestinationBounds = new Rect();
249 
250         private T mBaseValue;
251         protected T mCurrentValue;
252         protected T mStartValue;
253         private T mEndValue;
254         private float mStartingAngle;
255         private PipAnimationCallback mPipAnimationCallback;
256         private PipTransactionHandler mPipTransactionHandler;
257         private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
258                 mSurfaceControlTransactionFactory;
259         private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
260         private @TransitionDirection int mTransitionDirection;
261         protected SurfaceControl mContentOverlay;
262 
PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, T endValue, float startingAngle)263         private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
264                 @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
265                 T endValue, float startingAngle) {
266             mTaskInfo = taskInfo;
267             mLeash = leash;
268             mAnimationType = animationType;
269             mDestinationBounds.set(destinationBounds);
270             mBaseValue = baseValue;
271             mStartValue = startValue;
272             mEndValue = endValue;
273             mStartingAngle = startingAngle;
274             addListener(this);
275             addUpdateListener(this);
276             mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
277             mTransitionDirection = TRANSITION_DIRECTION_NONE;
278         }
279 
280         @Override
onAnimationStart(Animator animation)281         public void onAnimationStart(Animator animation) {
282             mCurrentValue = mStartValue;
283             onStartTransaction(mLeash, newSurfaceControlTransaction());
284             if (mPipAnimationCallback != null) {
285                 mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
286             }
287         }
288 
289         @Override
onAnimationUpdate(ValueAnimator animation)290         public void onAnimationUpdate(ValueAnimator animation) {
291             applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
292                     animation.getAnimatedFraction());
293         }
294 
295         @Override
onAnimationEnd(Animator animation)296         public void onAnimationEnd(Animator animation) {
297             mCurrentValue = mEndValue;
298             final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
299             onEndTransaction(mLeash, tx, mTransitionDirection);
300             if (mPipAnimationCallback != null) {
301                 mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
302             }
303             mTransitionDirection = TRANSITION_DIRECTION_NONE;
304         }
305 
306         @Override
onAnimationCancel(Animator animation)307         public void onAnimationCancel(Animator animation) {
308             if (mPipAnimationCallback != null) {
309                 mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this);
310             }
311             mTransitionDirection = TRANSITION_DIRECTION_NONE;
312         }
313 
onAnimationRepeat(Animator animation)314         @Override public void onAnimationRepeat(Animator animation) {}
315 
316         @VisibleForTesting
getAnimationType()317         @AnimationType public int getAnimationType() {
318             return mAnimationType;
319         }
320 
321         @VisibleForTesting
setPipAnimationCallback(PipAnimationCallback callback)322         public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
323             mPipAnimationCallback = callback;
324             return this;
325         }
326 
setPipTransactionHandler(PipTransactionHandler handler)327         PipTransitionAnimator<T> setPipTransactionHandler(PipTransactionHandler handler) {
328             mPipTransactionHandler = handler;
329             return this;
330         }
331 
handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)332         boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
333                 Rect destinationBounds) {
334             if (mPipTransactionHandler != null) {
335                 return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds);
336             }
337             return false;
338         }
339 
getContentOverlay()340         SurfaceControl getContentOverlay() {
341             return mContentOverlay;
342         }
343 
setUseContentOverlay(Context context)344         PipTransitionAnimator<T> setUseContentOverlay(Context context) {
345             final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
346             if (mContentOverlay != null) {
347                 // remove existing content overlay if there is any.
348                 tx.remove(mContentOverlay);
349                 tx.apply();
350             }
351             mContentOverlay = new SurfaceControl.Builder(new SurfaceSession())
352                     .setCallsite("PipAnimation")
353                     .setName("PipContentOverlay")
354                     .setColorLayer()
355                     .build();
356             tx.show(mContentOverlay);
357             tx.setLayer(mContentOverlay, Integer.MAX_VALUE);
358             tx.setColor(mContentOverlay, getContentOverlayColor(context));
359             tx.setAlpha(mContentOverlay, 0f);
360             tx.reparent(mContentOverlay, mLeash);
361             tx.apply();
362             return this;
363         }
364 
getContentOverlayColor(Context context)365         private float[] getContentOverlayColor(Context context) {
366             final TypedArray ta = context.obtainStyledAttributes(new int[] {
367                     android.R.attr.colorBackground });
368             try {
369                 int colorAccent = ta.getColor(0, 0);
370                 return new float[] {
371                         Color.red(colorAccent) / 255f,
372                         Color.green(colorAccent) / 255f,
373                         Color.blue(colorAccent) / 255f };
374             } finally {
375                 ta.recycle();
376             }
377         }
378 
379         /**
380          * Clears the {@link #mContentOverlay}, this should be done after the content overlay is
381          * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay}
382          */
clearContentOverlay()383         void clearContentOverlay() {
384             mContentOverlay = null;
385         }
386 
387         @VisibleForTesting
getTransitionDirection()388         @TransitionDirection public int getTransitionDirection() {
389             return mTransitionDirection;
390         }
391 
392         @VisibleForTesting
setTransitionDirection(@ransitionDirection int direction)393         public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
394             if (direction != TRANSITION_DIRECTION_SAME) {
395                 mTransitionDirection = direction;
396             }
397             return this;
398         }
399 
getStartValue()400         T getStartValue() {
401             return mStartValue;
402         }
403 
getBaseValue()404         T getBaseValue() {
405             return mBaseValue;
406         }
407 
408         @VisibleForTesting
getEndValue()409         public T getEndValue() {
410             return mEndValue;
411         }
412 
getDestinationBounds()413         Rect getDestinationBounds() {
414             return mDestinationBounds;
415         }
416 
setDestinationBounds(Rect destinationBounds)417         void setDestinationBounds(Rect destinationBounds) {
418             mDestinationBounds.set(destinationBounds);
419             if (mAnimationType == ANIM_TYPE_ALPHA) {
420                 onStartTransaction(mLeash, newSurfaceControlTransaction());
421             }
422         }
423 
setCurrentValue(T value)424         void setCurrentValue(T value) {
425             mCurrentValue = value;
426         }
427 
shouldApplyCornerRadius()428         boolean shouldApplyCornerRadius() {
429             return !isOutPipDirection(mTransitionDirection);
430         }
431 
inScaleTransition()432         boolean inScaleTransition() {
433             if (mAnimationType != ANIM_TYPE_BOUNDS) return false;
434             final int direction = getTransitionDirection();
435             return !isInPipDirection(direction) && !isOutPipDirection(direction);
436         }
437 
438         /**
439          * Updates the {@link #mEndValue}.
440          *
441          * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation.
442          * This is typically used when we receive a shelf height adjustment during the bounds
443          * animation. In which case we can update the end bounds and keep the existing animation
444          * running instead of cancelling it.
445          */
updateEndValue(T endValue)446         public void updateEndValue(T endValue) {
447             mEndValue = endValue;
448         }
449 
450         /**
451          * @return {@link SurfaceControl.Transaction} instance with vsync-id.
452          */
newSurfaceControlTransaction()453         protected SurfaceControl.Transaction newSurfaceControlTransaction() {
454             final SurfaceControl.Transaction tx =
455                     mSurfaceControlTransactionFactory.getTransaction();
456             tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
457             return tx;
458         }
459 
460         @VisibleForTesting
setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)461         public void setSurfaceControlTransactionFactory(
462                 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
463             mSurfaceControlTransactionFactory = factory;
464         }
465 
getSurfaceTransactionHelper()466         PipSurfaceTransactionHelper getSurfaceTransactionHelper() {
467             return mSurfaceTransactionHelper;
468         }
469 
setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper)470         void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) {
471             mSurfaceTransactionHelper = helper;
472         }
473 
onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)474         void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
475 
onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, @TransitionDirection int transitionDirection)476         void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
477                 @TransitionDirection int transitionDirection) {}
478 
applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)479         abstract void applySurfaceControlTransaction(SurfaceControl leash,
480                 SurfaceControl.Transaction tx, float fraction);
481 
ofAlpha(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float startValue, float endValue)482         static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash,
483                 Rect destinationBounds, float startValue, float endValue) {
484             return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA,
485                     destinationBounds, startValue, startValue, endValue, 0) {
486                 @Override
487                 void applySurfaceControlTransaction(SurfaceControl leash,
488                         SurfaceControl.Transaction tx, float fraction) {
489                     final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
490                     setCurrentValue(alpha);
491                     getSurfaceTransactionHelper().alpha(tx, leash, alpha)
492                             .round(tx, leash, shouldApplyCornerRadius());
493                     tx.apply();
494                 }
495 
496                 @Override
497                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
498                     if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) {
499                         // while removing the pip stack, no extra work needs to be done here.
500                         return;
501                     }
502                     getSurfaceTransactionHelper()
503                             .resetScale(tx, leash, getDestinationBounds())
504                             .crop(tx, leash, getDestinationBounds())
505                             .round(tx, leash, shouldApplyCornerRadius());
506                     tx.show(leash);
507                     tx.apply();
508                 }
509 
510                 @Override
511                 public void updateEndValue(Float endValue) {
512                     super.updateEndValue(endValue);
513                     mStartValue = mCurrentValue;
514                 }
515             };
516         }
517 
ofBounds(TaskInfo taskInfo, SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)518         static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
519                 Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
520                 @PipAnimationController.TransitionDirection int direction, float startingAngle,
521                 @Surface.Rotation int rotationDelta) {
522             final boolean isOutPipDirection = isOutPipDirection(direction);
523 
524             // Just for simplicity we'll interpolate between the source rect hint insets and empty
525             // insets to calculate the window crop
526             final Rect initialSourceValue;
527             if (isOutPipDirection) {
528                 initialSourceValue = new Rect(endValue);
529             } else {
530                 initialSourceValue = new Rect(baseValue);
531             }
532 
533             final Rect rotatedEndRect;
534             final Rect lastEndRect;
535             final Rect initialContainerRect;
536             if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
537                 lastEndRect = new Rect(endValue);
538                 rotatedEndRect = new Rect(endValue);
539                 // Rotate the end bounds according to the rotation delta because the display will
540                 // be rotated to the same orientation.
541                 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
542                 // Use the rect that has the same orientation as the hint rect.
543                 initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
544             } else {
545                 rotatedEndRect = lastEndRect = null;
546                 initialContainerRect = initialSourceValue;
547             }
548 
549             final Rect sourceHintRectInsets;
550             if (sourceHintRect == null) {
551                 sourceHintRectInsets = null;
552             } else {
553                 sourceHintRectInsets = new Rect(sourceHintRect.left - initialContainerRect.left,
554                         sourceHintRect.top - initialContainerRect.top,
555                         initialContainerRect.right - sourceHintRect.right,
556                         initialContainerRect.bottom - sourceHintRect.bottom);
557             }
558             final Rect zeroInsets = new Rect(0, 0, 0, 0);
559 
560             // construct new Rect instances in case they are recycled
561             return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
562                     endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue),
563                     startingAngle) {
564                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
565                 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
566 
567                 @Override
568                 void applySurfaceControlTransaction(SurfaceControl leash,
569                         SurfaceControl.Transaction tx, float fraction) {
570                     final Rect base = getBaseValue();
571                     final Rect start = getStartValue();
572                     final Rect end = getEndValue();
573                     if (mContentOverlay != null) {
574                         tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
575                     }
576                     if (rotatedEndRect != null) {
577                         // Animate the bounds in a different orientation. It only happens when
578                         // switching between PiP and fullscreen.
579                         applyRotation(tx, leash, fraction, start, end);
580                         return;
581                     }
582                     Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
583                     float angle = (1.0f - fraction) * startingAngle;
584                     setCurrentValue(bounds);
585                     if (inScaleTransition() || sourceHintRect == null) {
586                         if (isOutPipDirection) {
587                             getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
588                         } else {
589                             getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle)
590                                     .round(tx, leash, base, bounds);
591                         }
592                     } else {
593                         final Rect insets = computeInsets(fraction);
594                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
595                                 initialSourceValue, bounds, insets);
596                         if (shouldApplyCornerRadius()) {
597                             final Rect sourceBounds = new Rect(initialContainerRect);
598                             sourceBounds.inset(insets);
599                             getSurfaceTransactionHelper().round(tx, leash,
600                                     sourceBounds, bounds);
601                         }
602                     }
603                     if (!handlePipTransaction(leash, tx, bounds)) {
604                         tx.apply();
605                     }
606                 }
607 
608                 private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
609                         float fraction, Rect start, Rect end) {
610                     if (!end.equals(lastEndRect)) {
611                         // If the end bounds are changed during animating (e.g. shelf height), the
612                         // rotated end bounds also need to be updated.
613                         rotatedEndRect.set(endValue);
614                         rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
615                         lastEndRect.set(end);
616                     }
617                     final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect);
618                     setCurrentValue(bounds);
619                     final Rect insets = computeInsets(fraction);
620                     final float degree, x, y;
621                     if (Transitions.ENABLE_SHELL_TRANSITIONS) {
622                         if (rotationDelta == ROTATION_90) {
623                             degree = 90 * (1 - fraction);
624                             x = fraction * (end.left - start.left)
625                                     + start.left + start.right * (1 - fraction);
626                             y = fraction * (end.top - start.top) + start.top;
627                         } else {
628                             degree = -90 * (1 - fraction);
629                             x = fraction * (end.left - start.left) + start.left;
630                             y = fraction * (end.top - start.top)
631                                     + start.top + start.bottom * (1 - fraction);
632                         }
633                     } else {
634                         if (rotationDelta == ROTATION_90) {
635                             degree = 90 * fraction;
636                             x = fraction * (end.right - start.left) + start.left;
637                             y = fraction * (end.top - start.top) + start.top;
638                         } else {
639                             degree = -90 * fraction;
640                             x = fraction * (end.left - start.left) + start.left;
641                             y = fraction * (end.bottom - start.top) + start.top;
642                         }
643                     }
644                     final Rect sourceBounds = new Rect(initialContainerRect);
645                     sourceBounds.inset(insets);
646                     getSurfaceTransactionHelper()
647                             .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
648                                     insets, degree, x, y, isOutPipDirection,
649                                     rotationDelta == ROTATION_270 /* clockwise */)
650                             .round(tx, leash, sourceBounds, bounds);
651                     tx.apply();
652                 }
653 
654                 private Rect computeInsets(float fraction) {
655                     if (sourceHintRectInsets == null) {
656                         return zeroInsets;
657                     }
658                     final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets;
659                     final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets;
660                     return mInsetsEvaluator.evaluate(fraction, startRect, endRect);
661                 }
662 
663                 @Override
664                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
665                     getSurfaceTransactionHelper()
666                             .alpha(tx, leash, 1f)
667                             .round(tx, leash, shouldApplyCornerRadius());
668                     // TODO(b/178632364): this is a work around for the black background when
669                     // entering PiP in buttion navigation mode.
670                     if (isInPipDirection(direction)) {
671                         tx.setWindowCrop(leash, getStartValue());
672                     }
673                     tx.show(leash);
674                     tx.apply();
675                 }
676 
677                 @Override
678                 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
679                         int transitionDirection) {
680                     // NOTE: intentionally does not apply the transaction here.
681                     // this end transaction should get executed synchronously with the final
682                     // WindowContainerTransaction in task organizer
683                     final Rect destBounds = getDestinationBounds();
684                     getSurfaceTransactionHelper().resetScale(tx, leash, destBounds);
685                     if (isOutPipDirection(transitionDirection)) {
686                         // Exit pip, clear scale, position and crop.
687                         tx.setMatrix(leash, 1, 0, 0, 1);
688                         tx.setPosition(leash, 0, 0);
689                         tx.setWindowCrop(leash, 0, 0);
690                     } else {
691                         getSurfaceTransactionHelper().crop(tx, leash, destBounds);
692                     }
693                 }
694 
695                 @Override
696                 public void updateEndValue(Rect endValue) {
697                     super.updateEndValue(endValue);
698                     if (mStartValue != null && mCurrentValue != null) {
699                         mStartValue.set(mCurrentValue);
700                     }
701                 }
702             };
703         }
704     }
705 }
706