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