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.os.SystemClock;
34 import android.view.Surface;
35 import android.view.SurfaceControl;
36 import android.window.TaskSnapshot;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
40 import com.android.internal.protolog.common.ProtoLog;
41 import com.android.launcher3.icons.IconProvider;
42 import com.android.wm.shell.animation.Interpolators;
43 import com.android.wm.shell.common.pip.PipUtils;
44 import com.android.wm.shell.protolog.ShellProtoLogGroup;
45 import com.android.wm.shell.transition.Transitions;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.Objects;
50 
51 /**
52  * Controller class of PiP animations (both from and to PiP mode).
53  */
54 public class PipAnimationController {
55     static final float FRACTION_START = 0f;
56     static final float FRACTION_END = 1f;
57 
58     public static final int ANIM_TYPE_BOUNDS = 0;
59     public static final int ANIM_TYPE_ALPHA = 1;
60 
61     @IntDef(prefix = { "ANIM_TYPE_" }, value = {
62             ANIM_TYPE_BOUNDS,
63             ANIM_TYPE_ALPHA
64     })
65     @Retention(RetentionPolicy.SOURCE)
66     public @interface AnimationType {}
67 
68     /**
69      * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
70      * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
71      * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong
72      * animation style to an unrelated task.
73      */
74     private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800;
75 
76     public static final int TRANSITION_DIRECTION_NONE = 0;
77     public static final int TRANSITION_DIRECTION_SAME = 1;
78     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
79     public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3;
80     public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4;
81     public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
82     public static final int TRANSITION_DIRECTION_SNAP_AFTER_RESIZE = 6;
83     public static final int TRANSITION_DIRECTION_USER_RESIZE = 7;
84     public static final int TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND = 8;
85 
86     @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
87             TRANSITION_DIRECTION_NONE,
88             TRANSITION_DIRECTION_SAME,
89             TRANSITION_DIRECTION_TO_PIP,
90             TRANSITION_DIRECTION_LEAVE_PIP,
91             TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN,
92             TRANSITION_DIRECTION_REMOVE_STACK,
93             TRANSITION_DIRECTION_SNAP_AFTER_RESIZE,
94             TRANSITION_DIRECTION_USER_RESIZE,
95             TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND
96     })
97     @Retention(RetentionPolicy.SOURCE)
98     public @interface TransitionDirection {}
99 
isInPipDirection(@ransitionDirection int direction)100     public static boolean isInPipDirection(@TransitionDirection int direction) {
101         return direction == TRANSITION_DIRECTION_TO_PIP;
102     }
103 
isOutPipDirection(@ransitionDirection int direction)104     public static boolean isOutPipDirection(@TransitionDirection int direction) {
105         return direction == TRANSITION_DIRECTION_LEAVE_PIP
106                 || direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
107     }
108 
109     /** Whether the given direction represents removing PIP. */
isRemovePipDirection(@ransitionDirection int direction)110     public static boolean isRemovePipDirection(@TransitionDirection int direction) {
111         return direction == TRANSITION_DIRECTION_REMOVE_STACK;
112     }
113 
114     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
115 
116     private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
117             ThreadLocal.withInitial(() -> {
118                 AnimationHandler handler = new AnimationHandler();
119                 handler.setProvider(new SfVsyncFrameCallbackProvider());
120                 return handler;
121             });
122 
123     private PipTransitionAnimator mCurrentAnimator;
124     @AnimationType
125     private int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
126     private long mLastOneShotAlphaAnimationTime;
127 
PipAnimationController(PipSurfaceTransactionHelper helper)128     public PipAnimationController(PipSurfaceTransactionHelper helper) {
129         mSurfaceTransactionHelper = helper;
130     }
131 
132     @SuppressWarnings("unchecked")
133     @VisibleForTesting
getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float alphaStart, float alphaEnd)134     public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
135             Rect destinationBounds, float alphaStart, float alphaEnd) {
136         if (mCurrentAnimator == null) {
137             mCurrentAnimator = setupPipTransitionAnimator(
138                     PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
139                             alphaEnd));
140         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
141                 && Objects.equals(destinationBounds, mCurrentAnimator.getDestinationBounds())
142                 && mCurrentAnimator.isRunning()) {
143             mCurrentAnimator.updateEndValue(alphaEnd);
144         } else {
145             mCurrentAnimator.cancel();
146             mCurrentAnimator = setupPipTransitionAnimator(
147                     PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
148                             alphaEnd));
149         }
150         return mCurrentAnimator;
151     }
152 
153     @SuppressWarnings("unchecked")
154     /**
155      * Construct and return an animator that animates from the {@param startBounds} to the
156      * {@param endBounds} with the given {@param direction}. If {@param direction} is type
157      * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate
158      * in a better, more smooth manner. If the original bound was rotated and a reset needs to
159      * happen, pass in {@param startingAngle}.
160      *
161      * In the case where one wants to start animation during an intermediate animation (for example,
162      * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate
163      * to the correct snap fraction region), then provide the base bounds, which is current PiP
164      * leash bounds before transformation/any animation. This is so when we try to construct
165      * the different transformation matrices for the animation, we are constructing this based off
166      * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
167      *
168      * If non-zero {@param rotationDelta} is given, it means that the display will be rotated by
169      * leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the
170      * rotation change.
171      */
172     @VisibleForTesting
getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)173     public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
174             Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
175             @PipAnimationController.TransitionDirection int direction, float startingAngle,
176             @Surface.Rotation int rotationDelta) {
177         if (mCurrentAnimator == null) {
178             mCurrentAnimator = setupPipTransitionAnimator(
179                     PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
180                             endBounds, sourceHintRect, direction, 0 /* startingAngle */,
181                             rotationDelta));
182         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
183                 && mCurrentAnimator.isRunning()) {
184             // If we are still animating the fade into pip, then just move the surface and ensure
185             // we update with the new destination bounds, but don't interrupt the existing animation
186             // with a new bounds
187             mCurrentAnimator.setDestinationBounds(endBounds);
188         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS
189                 && mCurrentAnimator.isRunning()) {
190             mCurrentAnimator.setDestinationBounds(endBounds);
191             // construct new Rect instances in case they are recycled
192             mCurrentAnimator.updateEndValue(new Rect(endBounds));
193         } else {
194             mCurrentAnimator.cancel();
195             mCurrentAnimator = setupPipTransitionAnimator(
196                     PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
197                             endBounds, sourceHintRect, direction, startingAngle, rotationDelta));
198         }
199         return mCurrentAnimator;
200     }
201 
getCurrentAnimator()202     public PipTransitionAnimator getCurrentAnimator() {
203         return mCurrentAnimator;
204     }
205 
206     /** Reset animator state to prevent it from being used after its lifetime. */
resetAnimatorState()207     public void resetAnimatorState() {
208         mCurrentAnimator = null;
209     }
210 
setupPipTransitionAnimator(PipTransitionAnimator animator)211     private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
212         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
213         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
214         animator.setFloatValues(FRACTION_START, FRACTION_END);
215         animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
216         return animator;
217     }
218 
219     /**
220      * Returns true if the PiP window is currently being animated.
221      */
isAnimating()222     public boolean isAnimating() {
223         PipAnimationController.PipTransitionAnimator animator = getCurrentAnimator();
224         if (animator != null && animator.isRunning()) {
225             return true;
226         }
227         return false;
228     }
229 
230     /**
231      * Quietly cancel the animator by removing the listeners first.
232      */
quietCancel(@onNull ValueAnimator animator)233     static void quietCancel(@NonNull ValueAnimator animator) {
234         animator.removeAllUpdateListeners();
235         animator.removeAllListeners();
236         animator.cancel();
237     }
238 
239     /**
240      * Sets the preferred enter animation type for one time. This is typically used to set the
241      * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}.
242      * <p>
243      * For example, gesture navigation would first fade out the PiP activity, and the transition
244      * should be responsible to animate in (such as fade in) the PiP.
245      */
setOneShotEnterAnimationType(@nimationType int animationType)246     public void setOneShotEnterAnimationType(@AnimationType int animationType) {
247         mOneShotAnimationType = animationType;
248         if (animationType == ANIM_TYPE_ALPHA) {
249             mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
250         }
251     }
252 
253     /** Returns the preferred animation type and consumes the one-shot type if needed. */
254     @AnimationType
takeOneShotEnterAnimationType()255     public int takeOneShotEnterAnimationType() {
256         final int type = mOneShotAnimationType;
257         if (type == ANIM_TYPE_ALPHA) {
258             // Restore to default type.
259             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
260             if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
261                     > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
262                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
263                         "Alpha animation is expired. Use bounds animation.");
264                 return ANIM_TYPE_BOUNDS;
265             }
266         }
267         return type;
268     }
269 
270     /**
271      * Additional callback interface for PiP animation
272      */
273     public static class PipAnimationCallback {
274         /**
275          * Called when PiP animation is started.
276          */
onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator)277         public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {}
278 
279         /**
280          * Called when PiP animation is ended.
281          */
onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, PipTransitionAnimator animator)282         public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
283                 PipTransitionAnimator animator) {}
284 
285         /**
286          * Called when PiP animation is cancelled.
287          */
onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator)288         public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {}
289     }
290 
291     /**
292      * A handler class that could register itself to apply the transaction instead of the
293      * animation controller doing it. For example, the menu controller can be one such handler.
294      */
295     public static class PipTransactionHandler {
296 
297         /**
298          * Called when the animation controller is about to apply a transaction. Allow a registered
299          * handler to apply the transaction instead.
300          *
301          * @return true if handled by the handler, false otherwise.
302          */
handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds, float alpha)303         public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
304                 Rect destinationBounds, float alpha) {
305             return false;
306         }
307     }
308 
309     /**
310      * Animator for PiP transition animation which supports both alpha and bounds animation.
311      * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
312      */
313     public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
314             ValueAnimator.AnimatorUpdateListener,
315             ValueAnimator.AnimatorListener {
316         private final TaskInfo mTaskInfo;
317         private final SurfaceControl mLeash;
318         private final @AnimationType int mAnimationType;
319         private final Rect mDestinationBounds = new Rect();
320 
321         private T mBaseValue;
322         protected T mCurrentValue;
323         protected T mStartValue;
324         private T mEndValue;
325         private PipAnimationCallback mPipAnimationCallback;
326         private PipTransactionHandler mPipTransactionHandler;
327         private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
328                 mSurfaceControlTransactionFactory;
329         private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
330         private @TransitionDirection int mTransitionDirection;
331         protected PipContentOverlay mContentOverlay;
332         // Flag to avoid double-end
333         private boolean mHasRequestedEnd;
334 
PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, T endValue)335         private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
336                 @AnimationType int animationType,
337                 Rect destinationBounds, T baseValue, T startValue, T endValue) {
338             mTaskInfo = taskInfo;
339             mLeash = leash;
340             mAnimationType = animationType;
341             mDestinationBounds.set(destinationBounds);
342             mBaseValue = baseValue;
343             mStartValue = startValue;
344             mEndValue = endValue;
345             addListener(this);
346             addUpdateListener(this);
347             mSurfaceControlTransactionFactory =
348                     new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
349             mTransitionDirection = TRANSITION_DIRECTION_NONE;
350         }
351 
352         @Override
onAnimationStart(Animator animation)353         public void onAnimationStart(Animator animation) {
354             mCurrentValue = mStartValue;
355             onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
356             if (mPipAnimationCallback != null) {
357                 mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
358             }
359         }
360 
361         @Override
onAnimationUpdate(ValueAnimator animation)362         public void onAnimationUpdate(ValueAnimator animation) {
363             if (mHasRequestedEnd) return;
364             applySurfaceControlTransaction(mLeash,
365                     mSurfaceControlTransactionFactory.getTransaction(),
366                     animation.getAnimatedFraction());
367         }
368 
369         @Override
onAnimationEnd(Animator animation)370         public void onAnimationEnd(Animator animation) {
371             if (mHasRequestedEnd) return;
372             mHasRequestedEnd = true;
373             mCurrentValue = mEndValue;
374             final SurfaceControl.Transaction tx =
375                     mSurfaceControlTransactionFactory.getTransaction();
376             onEndTransaction(mLeash, tx, mTransitionDirection);
377             if (mPipAnimationCallback != null) {
378                 mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
379             }
380             mTransitionDirection = TRANSITION_DIRECTION_NONE;
381         }
382 
383         @Override
onAnimationCancel(Animator animation)384         public void onAnimationCancel(Animator animation) {
385             if (mPipAnimationCallback != null) {
386                 mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this);
387             }
388             mTransitionDirection = TRANSITION_DIRECTION_NONE;
389         }
390 
onAnimationRepeat(Animator animation)391         @Override public void onAnimationRepeat(Animator animation) {}
392 
393         @VisibleForTesting
getAnimationType()394         @AnimationType public int getAnimationType() {
395             return mAnimationType;
396         }
397 
398         @VisibleForTesting
setPipAnimationCallback(PipAnimationCallback callback)399         public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
400             mPipAnimationCallback = callback;
401             return this;
402         }
403 
setPipTransactionHandler(PipTransactionHandler handler)404         PipTransitionAnimator<T> setPipTransactionHandler(PipTransactionHandler handler) {
405             mPipTransactionHandler = handler;
406             return this;
407         }
408 
handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds, float alpha)409         boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
410                 Rect destinationBounds, float alpha) {
411             if (mPipTransactionHandler != null) {
412                 return mPipTransactionHandler.handlePipTransaction(
413                         leash, tx, destinationBounds, alpha);
414             }
415             return false;
416         }
417 
getContentOverlayLeash()418         SurfaceControl getContentOverlayLeash() {
419             return mContentOverlay == null ? null : mContentOverlay.mLeash;
420         }
421 
setColorContentOverlay(Context context)422         void setColorContentOverlay(Context context) {
423             reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context));
424         }
425 
setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint)426         void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
427             reattachContentOverlay(
428                     new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
429         }
430 
setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds, ActivityInfo activityInfo, int appIconSizePx)431         void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
432                 ActivityInfo activityInfo, int appIconSizePx) {
433             reattachContentOverlay(
434                     new PipContentOverlay.PipAppIconOverlay(context, appBounds, destinationBounds,
435                             new IconProvider(context).getIcon(activityInfo), appIconSizePx));
436         }
437 
reattachContentOverlay(PipContentOverlay overlay)438         private void reattachContentOverlay(PipContentOverlay overlay) {
439             final SurfaceControl.Transaction tx =
440                     mSurfaceControlTransactionFactory.getTransaction();
441             if (mContentOverlay != null) {
442                 mContentOverlay.detach(tx);
443             }
444             mContentOverlay = overlay;
445             mContentOverlay.attach(tx, mLeash);
446         }
447 
448         /**
449          * Clears the {@link #mContentOverlay}, this should be done after the content overlay is
450          * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay}
451          */
clearContentOverlay()452         void clearContentOverlay() {
453             mContentOverlay = null;
454         }
455 
456         @VisibleForTesting
getTransitionDirection()457         @TransitionDirection public int getTransitionDirection() {
458             return mTransitionDirection;
459         }
460 
461         @VisibleForTesting
setTransitionDirection(@ransitionDirection int direction)462         public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
463             if (direction != TRANSITION_DIRECTION_SAME) {
464                 mTransitionDirection = direction;
465             }
466             return this;
467         }
468 
getStartValue()469         T getStartValue() {
470             return mStartValue;
471         }
472 
getBaseValue()473         T getBaseValue() {
474             return mBaseValue;
475         }
476 
477         @VisibleForTesting
getEndValue()478         public T getEndValue() {
479             return mEndValue;
480         }
481 
getDestinationBounds()482         Rect getDestinationBounds() {
483             return mDestinationBounds;
484         }
485 
setDestinationBounds(Rect destinationBounds)486         void setDestinationBounds(Rect destinationBounds) {
487             mDestinationBounds.set(destinationBounds);
488             if (mAnimationType == ANIM_TYPE_ALPHA) {
489                 onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
490             }
491         }
492 
setCurrentValue(T value)493         void setCurrentValue(T value) {
494             mCurrentValue = value;
495         }
496 
shouldApplyCornerRadius()497         boolean shouldApplyCornerRadius() {
498             return !isOutPipDirection(mTransitionDirection);
499         }
500 
shouldApplyShadowRadius()501         boolean shouldApplyShadowRadius() {
502             return !isOutPipDirection(mTransitionDirection)
503                     && !isRemovePipDirection(mTransitionDirection);
504         }
505 
inScaleTransition()506         boolean inScaleTransition() {
507             if (mAnimationType != ANIM_TYPE_BOUNDS) return false;
508             final int direction = getTransitionDirection();
509             return !isInPipDirection(direction) && !isOutPipDirection(direction);
510         }
511 
512         /**
513          * Updates the {@link #mEndValue}.
514          *
515          * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation.
516          * This is typically used when we receive a shelf height adjustment during the bounds
517          * animation. In which case we can update the end bounds and keep the existing animation
518          * running instead of cancelling it.
519          */
updateEndValue(T endValue)520         public void updateEndValue(T endValue) {
521             mEndValue = endValue;
522         }
523 
524         @VisibleForTesting
setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)525         public void setSurfaceControlTransactionFactory(
526                 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
527             mSurfaceControlTransactionFactory = factory;
528         }
529 
getSurfaceTransactionHelper()530         PipSurfaceTransactionHelper getSurfaceTransactionHelper() {
531             return mSurfaceTransactionHelper;
532         }
533 
setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper)534         void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) {
535             mSurfaceTransactionHelper = helper;
536         }
537 
onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)538         void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
539 
onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, @TransitionDirection int transitionDirection)540         void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
541                 @TransitionDirection int transitionDirection) {}
542 
applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)543         abstract void applySurfaceControlTransaction(SurfaceControl leash,
544                 SurfaceControl.Transaction tx, float fraction);
545 
ofAlpha(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float startValue, float endValue)546         static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash,
547                 Rect destinationBounds, float startValue, float endValue) {
548             return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA,
549                     destinationBounds, startValue, startValue, endValue) {
550                 @Override
551                 void applySurfaceControlTransaction(SurfaceControl leash,
552                         SurfaceControl.Transaction tx, float fraction) {
553                     final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
554                     setCurrentValue(alpha);
555                     getSurfaceTransactionHelper().alpha(tx, leash, alpha)
556                             .round(tx, leash, shouldApplyCornerRadius())
557                             .shadow(tx, leash, shouldApplyShadowRadius());
558                     if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) {
559                         tx.apply();
560                     }
561                 }
562 
563                 @Override
564                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
565                     if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) {
566                         // while removing the pip stack, no extra work needs to be done here.
567                         return;
568                     }
569                     getSurfaceTransactionHelper()
570                             .resetScale(tx, leash, getDestinationBounds())
571                             .crop(tx, leash, getDestinationBounds())
572                             .round(tx, leash, shouldApplyCornerRadius())
573                             .shadow(tx, leash, shouldApplyShadowRadius());
574                     tx.show(leash);
575                     tx.apply();
576                 }
577 
578                 @Override
579                 public void updateEndValue(Float endValue) {
580                     super.updateEndValue(endValue);
581                     mStartValue = mCurrentValue;
582                 }
583             };
584         }
585 
ofBounds(TaskInfo taskInfo, SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceRectHint, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)586         static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
587                 Rect baseValue, Rect startValue, Rect endValue, Rect sourceRectHint,
588                 @PipAnimationController.TransitionDirection int direction, float startingAngle,
589                 @Surface.Rotation int rotationDelta) {
590             final boolean isOutPipDirection = isOutPipDirection(direction);
591             final boolean isInPipDirection = isInPipDirection(direction);
592             // Just for simplicity we'll interpolate between the source rect hint insets and empty
593             // insets to calculate the window crop
594             final Rect initialSourceValue;
595             if (isOutPipDirection) {
596                 initialSourceValue = new Rect(endValue);
597             } else {
598                 initialSourceValue = new Rect(baseValue);
599             }
600 
601             final Rect rotatedEndRect;
602             final Rect lastEndRect;
603             final Rect initialContainerRect;
604             if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
605                 lastEndRect = new Rect(endValue);
606                 rotatedEndRect = new Rect(endValue);
607                 // Rotate the end bounds according to the rotation delta because the display will
608                 // be rotated to the same orientation.
609                 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
610                 // Use the rect that has the same orientation as the hint rect.
611                 initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
612             } else {
613                 rotatedEndRect = lastEndRect = null;
614                 initialContainerRect = initialSourceValue;
615             }
616 
617             final Rect adjustedSourceRectHint = new Rect();
618             if (sourceRectHint == null || sourceRectHint.isEmpty()) {
619                 // Crop a Rect matches the aspect ratio and pivots at the center point.
620                 // This is done for entering case only.
621                 if (isInPipDirection(direction)) {
622                     final float aspectRatio = endValue.width() / (float) endValue.height();
623                     adjustedSourceRectHint.set(PipUtils.getEnterPipWithOverlaySrcRectHint(
624                             startValue, aspectRatio));
625                 }
626             } else {
627                 adjustedSourceRectHint.set(sourceRectHint);
628             }
629             final Rect sourceHintRectInsets = new Rect();
630             if (!adjustedSourceRectHint.isEmpty()) {
631                 sourceHintRectInsets.set(
632                         adjustedSourceRectHint.left - initialContainerRect.left,
633                         adjustedSourceRectHint.top - initialContainerRect.top,
634                         initialContainerRect.right - adjustedSourceRectHint.right,
635                         initialContainerRect.bottom - adjustedSourceRectHint.bottom);
636             }
637             final Rect zeroInsets = new Rect(0, 0, 0, 0);
638 
639             // construct new Rect instances in case they are recycled
640             return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
641                     endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) {
642                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
643                 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
644 
645                 @Override
646                 void applySurfaceControlTransaction(SurfaceControl leash,
647                         SurfaceControl.Transaction tx, float fraction) {
648                     final Rect base = getBaseValue();
649                     final Rect start = getStartValue();
650                     final Rect end = getEndValue();
651                     Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
652                     if (mContentOverlay != null) {
653                         mContentOverlay.onAnimationUpdate(tx, bounds, fraction);
654                     }
655                     if (rotatedEndRect != null) {
656                         // Animate the bounds in a different orientation. It only happens when
657                         // switching between PiP and fullscreen.
658                         applyRotation(tx, leash, fraction, start, end);
659                         return;
660                     }
661                     float angle = (1.0f - fraction) * startingAngle;
662                     setCurrentValue(bounds);
663                     if (inScaleTransition() || adjustedSourceRectHint.isEmpty()) {
664                         if (isOutPipDirection) {
665                             getSurfaceTransactionHelper().crop(tx, leash, end)
666                                     .scale(tx, leash, end, bounds);
667                         } else {
668                             getSurfaceTransactionHelper().crop(tx, leash, base)
669                                     .scale(tx, leash, base, bounds, angle)
670                                     .round(tx, leash, base, bounds)
671                                     .shadow(tx, leash, shouldApplyShadowRadius());
672                         }
673                     } else {
674                         final Rect insets = computeInsets(fraction);
675                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
676                                 adjustedSourceRectHint, initialSourceValue, bounds, insets,
677                                 isInPipDirection, fraction);
678                         if (shouldApplyCornerRadius()) {
679                             final Rect sourceBounds = new Rect(initialContainerRect);
680                             sourceBounds.inset(insets);
681                             getSurfaceTransactionHelper()
682                                     .round(tx, leash, sourceBounds, bounds)
683                                     .shadow(tx, leash, shouldApplyShadowRadius());
684                         }
685                     }
686                     if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) {
687                         tx.apply();
688                     }
689                 }
690 
691                 private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
692                         float fraction, Rect start, Rect end) {
693                     if (!end.equals(lastEndRect)) {
694                         // If the end bounds are changed during animating (e.g. shelf height), the
695                         // rotated end bounds also need to be updated.
696                         rotatedEndRect.set(endValue);
697                         rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
698                         lastEndRect.set(end);
699                     }
700                     final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect);
701                     setCurrentValue(bounds);
702                     final Rect insets = computeInsets(fraction);
703                     final float degree, x, y;
704                     if (Transitions.SHELL_TRANSITIONS_ROTATION) {
705                         if (rotationDelta == ROTATION_90) {
706                             degree = 90 * (1 - fraction);
707                             x = fraction * (end.left - start.left)
708                                     + start.left + start.width() * (1 - fraction);
709                             y = fraction * (end.top - start.top) + start.top;
710                         } else {
711                             degree = -90 * (1 - fraction);
712                             x = fraction * (end.left - start.left) + start.left;
713                             y = fraction * (end.top - start.top)
714                                     + start.top + start.height() * (1 - fraction);
715                         }
716                     } else {
717                         if (rotationDelta == ROTATION_90) {
718                             degree = 90 * fraction;
719                             x = fraction * (end.right - start.left) + start.left;
720                             y = fraction * (end.top - start.top) + start.top;
721                         } else {
722                             degree = -90 * fraction;
723                             x = fraction * (end.left - start.left) + start.left;
724                             y = fraction * (end.bottom - start.top) + start.top;
725                         }
726                     }
727                     final Rect sourceBounds = new Rect(initialContainerRect);
728                     sourceBounds.inset(insets);
729                     getSurfaceTransactionHelper()
730                             .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
731                                     insets, degree, x, y, isOutPipDirection,
732                                     rotationDelta == ROTATION_270 /* clockwise */);
733                     if (shouldApplyCornerRadius()) {
734                         getSurfaceTransactionHelper()
735                                 .round(tx, leash, sourceBounds, bounds)
736                                 .shadow(tx, leash, shouldApplyShadowRadius());
737                     }
738                     if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) {
739                         tx.apply();
740                     }
741                 }
742 
743                 private Rect computeInsets(float fraction) {
744                     final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets;
745                     final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets;
746                     return mInsetsEvaluator.evaluate(fraction, startRect, endRect);
747                 }
748 
749                 @Override
750                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
751                     getSurfaceTransactionHelper()
752                             .alpha(tx, leash, 1f)
753                             .round(tx, leash, shouldApplyCornerRadius())
754                             .shadow(tx, leash, shouldApplyShadowRadius());
755                     tx.show(leash);
756                     tx.apply();
757                 }
758 
759                 @Override
760                 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
761                         int transitionDirection) {
762                     // NOTE: intentionally does not apply the transaction here.
763                     // this end transaction should get executed synchronously with the final
764                     // WindowContainerTransaction in task organizer
765                     final Rect destBounds = getDestinationBounds();
766                     getSurfaceTransactionHelper().resetScale(tx, leash, destBounds);
767                     if (isOutPipDirection(transitionDirection)) {
768                         // Exit pip, clear scale, position and crop.
769                         tx.setMatrix(leash, 1, 0, 0, 1);
770                         tx.setPosition(leash, 0, 0);
771                         tx.setWindowCrop(leash, 0, 0);
772                     } else {
773                         getSurfaceTransactionHelper().crop(tx, leash, destBounds);
774                     }
775                     if (mContentOverlay != null) {
776                         clearContentOverlay();
777                     }
778                 }
779 
780                 @Override
781                 public void updateEndValue(Rect endValue) {
782                     super.updateEndValue(endValue);
783                     if (mStartValue != null && mCurrentValue != null) {
784                         mStartValue.set(mCurrentValue);
785                     }
786                 }
787             };
788         }
789     }
790 }
791