• 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.annotation.NonNull;
29 import android.app.TaskInfo;
30 import android.content.Context;
31 import android.content.pm.ActivityInfo;
32 import android.graphics.Rect;
33 import android.view.Surface;
34 import android.view.SurfaceControl;
35 import android.window.TaskSnapshot;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
39 import com.android.launcher3.icons.IconProvider;
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     public 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      * Returns true if the PiP window is currently being animated.
201      */
isAnimating()202     public boolean isAnimating() {
203         PipAnimationController.PipTransitionAnimator animator = getCurrentAnimator();
204         if (animator != null && animator.isRunning()) {
205             return true;
206         }
207         return false;
208     }
209 
210     /**
211      * Quietly cancel the animator by removing the listeners first.
212      */
quietCancel(@onNull ValueAnimator animator)213     static void quietCancel(@NonNull ValueAnimator animator) {
214         animator.removeAllUpdateListeners();
215         animator.removeAllListeners();
216         animator.cancel();
217     }
218 
219     /**
220      * Additional callback interface for PiP animation
221      */
222     public static class PipAnimationCallback {
223         /**
224          * Called when PiP animation is started.
225          */
onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator)226         public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {}
227 
228         /**
229          * Called when PiP animation is ended.
230          */
onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, PipTransitionAnimator animator)231         public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
232                 PipTransitionAnimator animator) {}
233 
234         /**
235          * Called when PiP animation is cancelled.
236          */
onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator)237         public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {}
238     }
239 
240     /**
241      * A handler class that could register itself to apply the transaction instead of the
242      * animation controller doing it. For example, the menu controller can be one such handler.
243      */
244     public static class PipTransactionHandler {
245 
246         /**
247          * Called when the animation controller is about to apply a transaction. Allow a registered
248          * handler to apply the transaction instead.
249          *
250          * @return true if handled by the handler, false otherwise.
251          */
handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)252         public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
253                 Rect destinationBounds) {
254             return false;
255         }
256     }
257 
258     /**
259      * Animator for PiP transition animation which supports both alpha and bounds animation.
260      * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
261      */
262     public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
263             ValueAnimator.AnimatorUpdateListener,
264             ValueAnimator.AnimatorListener {
265         private final TaskInfo mTaskInfo;
266         private final SurfaceControl mLeash;
267         private final @AnimationType int mAnimationType;
268         private final Rect mDestinationBounds = new Rect();
269 
270         private T mBaseValue;
271         protected T mCurrentValue;
272         protected T mStartValue;
273         private T mEndValue;
274         private PipAnimationCallback mPipAnimationCallback;
275         private PipTransactionHandler mPipTransactionHandler;
276         private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
277                 mSurfaceControlTransactionFactory;
278         private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
279         private @TransitionDirection int mTransitionDirection;
280         protected PipContentOverlay mContentOverlay;
281 
PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, T endValue)282         private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
283                 @AnimationType int animationType,
284                 Rect destinationBounds, T baseValue, T startValue, T endValue) {
285             mTaskInfo = taskInfo;
286             mLeash = leash;
287             mAnimationType = animationType;
288             mDestinationBounds.set(destinationBounds);
289             mBaseValue = baseValue;
290             mStartValue = startValue;
291             mEndValue = endValue;
292             addListener(this);
293             addUpdateListener(this);
294             mSurfaceControlTransactionFactory =
295                     new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
296             mTransitionDirection = TRANSITION_DIRECTION_NONE;
297         }
298 
299         @Override
onAnimationStart(Animator animation)300         public void onAnimationStart(Animator animation) {
301             mCurrentValue = mStartValue;
302             onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
303             if (mPipAnimationCallback != null) {
304                 mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
305             }
306         }
307 
308         @Override
onAnimationUpdate(ValueAnimator animation)309         public void onAnimationUpdate(ValueAnimator animation) {
310             applySurfaceControlTransaction(mLeash,
311                     mSurfaceControlTransactionFactory.getTransaction(),
312                     animation.getAnimatedFraction());
313         }
314 
315         @Override
onAnimationEnd(Animator animation)316         public void onAnimationEnd(Animator animation) {
317             mCurrentValue = mEndValue;
318             final SurfaceControl.Transaction tx =
319                     mSurfaceControlTransactionFactory.getTransaction();
320             onEndTransaction(mLeash, tx, mTransitionDirection);
321             if (mPipAnimationCallback != null) {
322                 mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
323             }
324             mTransitionDirection = TRANSITION_DIRECTION_NONE;
325         }
326 
327         @Override
onAnimationCancel(Animator animation)328         public void onAnimationCancel(Animator animation) {
329             if (mPipAnimationCallback != null) {
330                 mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this);
331             }
332             mTransitionDirection = TRANSITION_DIRECTION_NONE;
333         }
334 
onAnimationRepeat(Animator animation)335         @Override public void onAnimationRepeat(Animator animation) {}
336 
337         @VisibleForTesting
getAnimationType()338         @AnimationType public int getAnimationType() {
339             return mAnimationType;
340         }
341 
342         @VisibleForTesting
setPipAnimationCallback(PipAnimationCallback callback)343         public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
344             mPipAnimationCallback = callback;
345             return this;
346         }
347 
setPipTransactionHandler(PipTransactionHandler handler)348         PipTransitionAnimator<T> setPipTransactionHandler(PipTransactionHandler handler) {
349             mPipTransactionHandler = handler;
350             return this;
351         }
352 
handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)353         boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
354                 Rect destinationBounds) {
355             if (mPipTransactionHandler != null) {
356                 return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds);
357             }
358             return false;
359         }
360 
getContentOverlayLeash()361         SurfaceControl getContentOverlayLeash() {
362             return mContentOverlay == null ? null : mContentOverlay.mLeash;
363         }
364 
setColorContentOverlay(Context context)365         void setColorContentOverlay(Context context) {
366             reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context));
367         }
368 
setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint)369         void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
370             reattachContentOverlay(
371                     new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
372         }
373 
setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo, int appIconSizePx)374         void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo,
375                 int appIconSizePx) {
376             reattachContentOverlay(
377                     new PipContentOverlay.PipAppIconOverlay(context, bounds,
378                             new IconProvider(context).getIcon(activityInfo), appIconSizePx));
379         }
380 
reattachContentOverlay(PipContentOverlay overlay)381         private void reattachContentOverlay(PipContentOverlay overlay) {
382             final SurfaceControl.Transaction tx =
383                     mSurfaceControlTransactionFactory.getTransaction();
384             if (mContentOverlay != null) {
385                 mContentOverlay.detach(tx);
386             }
387             mContentOverlay = overlay;
388             mContentOverlay.attach(tx, mLeash);
389         }
390 
391         /**
392          * Clears the {@link #mContentOverlay}, this should be done after the content overlay is
393          * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay}
394          */
clearContentOverlay()395         void clearContentOverlay() {
396             mContentOverlay = null;
397         }
398 
399         @VisibleForTesting
getTransitionDirection()400         @TransitionDirection public int getTransitionDirection() {
401             return mTransitionDirection;
402         }
403 
404         @VisibleForTesting
setTransitionDirection(@ransitionDirection int direction)405         public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
406             if (direction != TRANSITION_DIRECTION_SAME) {
407                 mTransitionDirection = direction;
408             }
409             return this;
410         }
411 
getStartValue()412         T getStartValue() {
413             return mStartValue;
414         }
415 
getBaseValue()416         T getBaseValue() {
417             return mBaseValue;
418         }
419 
420         @VisibleForTesting
getEndValue()421         public T getEndValue() {
422             return mEndValue;
423         }
424 
getDestinationBounds()425         Rect getDestinationBounds() {
426             return mDestinationBounds;
427         }
428 
setDestinationBounds(Rect destinationBounds)429         void setDestinationBounds(Rect destinationBounds) {
430             mDestinationBounds.set(destinationBounds);
431             if (mAnimationType == ANIM_TYPE_ALPHA) {
432                 onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
433             }
434         }
435 
setCurrentValue(T value)436         void setCurrentValue(T value) {
437             mCurrentValue = value;
438         }
439 
shouldApplyCornerRadius()440         boolean shouldApplyCornerRadius() {
441             return !isOutPipDirection(mTransitionDirection);
442         }
443 
shouldApplyShadowRadius()444         boolean shouldApplyShadowRadius() {
445             return !isOutPipDirection(mTransitionDirection)
446                     && !isRemovePipDirection(mTransitionDirection);
447         }
448 
inScaleTransition()449         boolean inScaleTransition() {
450             if (mAnimationType != ANIM_TYPE_BOUNDS) return false;
451             final int direction = getTransitionDirection();
452             return !isInPipDirection(direction) && !isOutPipDirection(direction);
453         }
454 
455         /**
456          * Updates the {@link #mEndValue}.
457          *
458          * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation.
459          * This is typically used when we receive a shelf height adjustment during the bounds
460          * animation. In which case we can update the end bounds and keep the existing animation
461          * running instead of cancelling it.
462          */
updateEndValue(T endValue)463         public void updateEndValue(T endValue) {
464             mEndValue = endValue;
465         }
466 
467         @VisibleForTesting
setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)468         public void setSurfaceControlTransactionFactory(
469                 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
470             mSurfaceControlTransactionFactory = factory;
471         }
472 
getSurfaceTransactionHelper()473         PipSurfaceTransactionHelper getSurfaceTransactionHelper() {
474             return mSurfaceTransactionHelper;
475         }
476 
setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper)477         void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) {
478             mSurfaceTransactionHelper = helper;
479         }
480 
onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)481         void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
482 
onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, @TransitionDirection int transitionDirection)483         void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
484                 @TransitionDirection int transitionDirection) {}
485 
applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)486         abstract void applySurfaceControlTransaction(SurfaceControl leash,
487                 SurfaceControl.Transaction tx, float fraction);
488 
ofAlpha(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float startValue, float endValue)489         static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash,
490                 Rect destinationBounds, float startValue, float endValue) {
491             return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA,
492                     destinationBounds, startValue, startValue, endValue) {
493                 @Override
494                 void applySurfaceControlTransaction(SurfaceControl leash,
495                         SurfaceControl.Transaction tx, float fraction) {
496                     final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
497                     setCurrentValue(alpha);
498                     getSurfaceTransactionHelper().alpha(tx, leash, alpha)
499                             .round(tx, leash, shouldApplyCornerRadius())
500                             .shadow(tx, leash, shouldApplyShadowRadius());
501                     tx.apply();
502                 }
503 
504                 @Override
505                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
506                     if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) {
507                         // while removing the pip stack, no extra work needs to be done here.
508                         return;
509                     }
510                     getSurfaceTransactionHelper()
511                             .resetScale(tx, leash, getDestinationBounds())
512                             .crop(tx, leash, getDestinationBounds())
513                             .round(tx, leash, shouldApplyCornerRadius())
514                             .shadow(tx, leash, shouldApplyShadowRadius());
515                     tx.show(leash);
516                     tx.apply();
517                 }
518 
519                 @Override
520                 public void updateEndValue(Float endValue) {
521                     super.updateEndValue(endValue);
522                     mStartValue = mCurrentValue;
523                 }
524             };
525         }
526 
ofBounds(TaskInfo taskInfo, SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)527         static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
528                 Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
529                 @PipAnimationController.TransitionDirection int direction, float startingAngle,
530                 @Surface.Rotation int rotationDelta) {
531             final boolean isOutPipDirection = isOutPipDirection(direction);
532             final boolean isInPipDirection = isInPipDirection(direction);
533             // Just for simplicity we'll interpolate between the source rect hint insets and empty
534             // insets to calculate the window crop
535             final Rect initialSourceValue;
536             if (isOutPipDirection) {
537                 initialSourceValue = new Rect(endValue);
538             } else {
539                 initialSourceValue = new Rect(baseValue);
540             }
541 
542             final Rect rotatedEndRect;
543             final Rect lastEndRect;
544             final Rect initialContainerRect;
545             if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
546                 lastEndRect = new Rect(endValue);
547                 rotatedEndRect = new Rect(endValue);
548                 // Rotate the end bounds according to the rotation delta because the display will
549                 // be rotated to the same orientation.
550                 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
551                 // Use the rect that has the same orientation as the hint rect.
552                 initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
553             } else {
554                 rotatedEndRect = lastEndRect = null;
555                 initialContainerRect = initialSourceValue;
556             }
557 
558             final Rect sourceHintRectInsets;
559             if (sourceHintRect == null) {
560                 sourceHintRectInsets = null;
561             } else {
562                 sourceHintRectInsets = new Rect(sourceHintRect.left - initialContainerRect.left,
563                         sourceHintRect.top - initialContainerRect.top,
564                         initialContainerRect.right - sourceHintRect.right,
565                         initialContainerRect.bottom - sourceHintRect.bottom);
566             }
567             final Rect zeroInsets = new Rect(0, 0, 0, 0);
568 
569             // construct new Rect instances in case they are recycled
570             return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
571                     endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) {
572                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
573                 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
574 
575                 @Override
576                 void applySurfaceControlTransaction(SurfaceControl leash,
577                         SurfaceControl.Transaction tx, float fraction) {
578                     final Rect base = getBaseValue();
579                     final Rect start = getStartValue();
580                     final Rect end = getEndValue();
581                     Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
582                     if (mContentOverlay != null) {
583                         mContentOverlay.onAnimationUpdate(tx, bounds, fraction);
584                     }
585                     if (rotatedEndRect != null) {
586                         // Animate the bounds in a different orientation. It only happens when
587                         // switching between PiP and fullscreen.
588                         applyRotation(tx, leash, fraction, start, end);
589                         return;
590                     }
591                     float angle = (1.0f - fraction) * startingAngle;
592                     setCurrentValue(bounds);
593                     if (inScaleTransition() || sourceHintRect == null) {
594                         if (isOutPipDirection) {
595                             getSurfaceTransactionHelper().crop(tx, leash, end)
596                                     .scale(tx, leash, end, bounds);
597                         } else {
598                             getSurfaceTransactionHelper().crop(tx, leash, base)
599                                     .scale(tx, leash, base, bounds, angle)
600                                     .round(tx, leash, base, bounds)
601                                     .shadow(tx, leash, shouldApplyShadowRadius());
602                         }
603                     } else {
604                         final Rect insets = computeInsets(fraction);
605                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
606                                 sourceHintRect, initialSourceValue, bounds, insets,
607                                 isInPipDirection, fraction);
608                         if (shouldApplyCornerRadius()) {
609                             final Rect sourceBounds = new Rect(initialContainerRect);
610                             sourceBounds.inset(insets);
611                             getSurfaceTransactionHelper()
612                                     .round(tx, leash, sourceBounds, bounds)
613                                     .shadow(tx, leash, shouldApplyShadowRadius());
614                         }
615                     }
616                     if (!handlePipTransaction(leash, tx, bounds)) {
617                         tx.apply();
618                     }
619                 }
620 
621                 private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
622                         float fraction, Rect start, Rect end) {
623                     if (!end.equals(lastEndRect)) {
624                         // If the end bounds are changed during animating (e.g. shelf height), the
625                         // rotated end bounds also need to be updated.
626                         rotatedEndRect.set(endValue);
627                         rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
628                         lastEndRect.set(end);
629                     }
630                     final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect);
631                     setCurrentValue(bounds);
632                     final Rect insets = computeInsets(fraction);
633                     final float degree, x, y;
634                     if (Transitions.SHELL_TRANSITIONS_ROTATION) {
635                         if (rotationDelta == ROTATION_90) {
636                             degree = 90 * (1 - fraction);
637                             x = fraction * (end.left - start.left)
638                                     + start.left + start.width() * (1 - fraction);
639                             y = fraction * (end.top - start.top) + start.top;
640                         } else {
641                             degree = -90 * (1 - fraction);
642                             x = fraction * (end.left - start.left) + start.left;
643                             y = fraction * (end.top - start.top)
644                                     + start.top + start.height() * (1 - fraction);
645                         }
646                     } else {
647                         if (rotationDelta == ROTATION_90) {
648                             degree = 90 * fraction;
649                             x = fraction * (end.right - start.left) + start.left;
650                             y = fraction * (end.top - start.top) + start.top;
651                         } else {
652                             degree = -90 * fraction;
653                             x = fraction * (end.left - start.left) + start.left;
654                             y = fraction * (end.bottom - start.top) + start.top;
655                         }
656                     }
657                     final Rect sourceBounds = new Rect(initialContainerRect);
658                     sourceBounds.inset(insets);
659                     getSurfaceTransactionHelper()
660                             .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
661                                     insets, degree, x, y, isOutPipDirection,
662                                     rotationDelta == ROTATION_270 /* clockwise */);
663                     if (shouldApplyCornerRadius()) {
664                         getSurfaceTransactionHelper()
665                                 .round(tx, leash, sourceBounds, bounds)
666                                 .shadow(tx, leash, shouldApplyShadowRadius());
667                     }
668                     tx.apply();
669                 }
670 
671                 private Rect computeInsets(float fraction) {
672                     if (sourceHintRectInsets == null) {
673                         return zeroInsets;
674                     }
675                     final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets;
676                     final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets;
677                     return mInsetsEvaluator.evaluate(fraction, startRect, endRect);
678                 }
679 
680                 @Override
681                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
682                     getSurfaceTransactionHelper()
683                             .alpha(tx, leash, 1f)
684                             .round(tx, leash, shouldApplyCornerRadius())
685                             .shadow(tx, leash, shouldApplyShadowRadius());
686                     // TODO(b/178632364): this is a work around for the black background when
687                     // entering PiP in button navigation mode.
688                     if (isInPipDirection(direction)) {
689                         tx.setWindowCrop(leash, getStartValue());
690                     }
691                     tx.show(leash);
692                     tx.apply();
693                 }
694 
695                 @Override
696                 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
697                         int transitionDirection) {
698                     // NOTE: intentionally does not apply the transaction here.
699                     // this end transaction should get executed synchronously with the final
700                     // WindowContainerTransaction in task organizer
701                     final Rect destBounds = getDestinationBounds();
702                     getSurfaceTransactionHelper().resetScale(tx, leash, destBounds);
703                     if (isOutPipDirection(transitionDirection)) {
704                         // Exit pip, clear scale, position and crop.
705                         tx.setMatrix(leash, 1, 0, 0, 1);
706                         tx.setPosition(leash, 0, 0);
707                         tx.setWindowCrop(leash, 0, 0);
708                     } else {
709                         getSurfaceTransactionHelper().crop(tx, leash, destBounds);
710                     }
711                     if (mContentOverlay != null) {
712                         mContentOverlay.onAnimationEnd(tx, destBounds);
713                     }
714                 }
715 
716                 @Override
717                 public void updateEndValue(Rect endValue) {
718                     super.updateEndValue(endValue);
719                     if (mStartValue != null && mCurrentValue != null) {
720                         mStartValue.set(mCurrentValue);
721                     }
722                 }
723             };
724         }
725     }
726 }
727