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