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