• 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.systemui.pip;
18 
19 import android.animation.AnimationHandler;
20 import android.animation.Animator;
21 import android.animation.RectEvaluator;
22 import android.animation.ValueAnimator;
23 import android.annotation.IntDef;
24 import android.graphics.Rect;
25 import android.view.SurfaceControl;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
29 import com.android.systemui.Interpolators;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 
34 import javax.inject.Inject;
35 
36 /**
37  * Controller class of PiP animations (both from and to PiP mode).
38  */
39 public class PipAnimationController {
40     private static final float FRACTION_START = 0f;
41     private static final float FRACTION_END = 1f;
42 
43     public static final int ANIM_TYPE_BOUNDS = 0;
44     public static final int ANIM_TYPE_ALPHA = 1;
45 
46     @IntDef(prefix = { "ANIM_TYPE_" }, value = {
47             ANIM_TYPE_BOUNDS,
48             ANIM_TYPE_ALPHA
49     })
50     @Retention(RetentionPolicy.SOURCE)
51     public @interface AnimationType {}
52 
53     public static final int TRANSITION_DIRECTION_NONE = 0;
54     public static final int TRANSITION_DIRECTION_SAME = 1;
55     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
56     public static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3;
57     public static final int TRANSITION_DIRECTION_TO_SPLIT_SCREEN = 4;
58     public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
59 
60     @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
61             TRANSITION_DIRECTION_NONE,
62             TRANSITION_DIRECTION_SAME,
63             TRANSITION_DIRECTION_TO_PIP,
64             TRANSITION_DIRECTION_TO_FULLSCREEN,
65             TRANSITION_DIRECTION_TO_SPLIT_SCREEN,
66             TRANSITION_DIRECTION_REMOVE_STACK
67     })
68     @Retention(RetentionPolicy.SOURCE)
69     public @interface TransitionDirection {}
70 
isInPipDirection(@ransitionDirection int direction)71     public static boolean isInPipDirection(@TransitionDirection int direction) {
72         return direction == TRANSITION_DIRECTION_TO_PIP;
73     }
74 
isOutPipDirection(@ransitionDirection int direction)75     public static boolean isOutPipDirection(@TransitionDirection int direction) {
76         return direction == TRANSITION_DIRECTION_TO_FULLSCREEN
77                 || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
78     }
79 
80     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
81 
82     private PipTransitionAnimator mCurrentAnimator;
83 
84     private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
85             ThreadLocal.withInitial(() -> {
86                 AnimationHandler handler = new AnimationHandler();
87                 handler.setProvider(new SfVsyncFrameCallbackProvider());
88                 return handler;
89             });
90 
91     @Inject
PipAnimationController(PipSurfaceTransactionHelper helper)92     PipAnimationController(PipSurfaceTransactionHelper helper) {
93         mSurfaceTransactionHelper = helper;
94     }
95 
96     @SuppressWarnings("unchecked")
getAnimator(SurfaceControl leash, Rect destinationBounds, float alphaStart, float alphaEnd)97     PipTransitionAnimator getAnimator(SurfaceControl leash,
98             Rect destinationBounds, float alphaStart, float alphaEnd) {
99         if (mCurrentAnimator == null) {
100             mCurrentAnimator = setupPipTransitionAnimator(
101                     PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
102         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
103                 && mCurrentAnimator.isRunning()) {
104             mCurrentAnimator.updateEndValue(alphaEnd);
105         } else {
106             mCurrentAnimator.cancel();
107             mCurrentAnimator = setupPipTransitionAnimator(
108                     PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
109         }
110         return mCurrentAnimator;
111     }
112 
113     @SuppressWarnings("unchecked")
getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, Rect sourceHintRect)114     PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
115             Rect sourceHintRect) {
116         if (mCurrentAnimator == null) {
117             mCurrentAnimator = setupPipTransitionAnimator(
118                     PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
119         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
120                 && mCurrentAnimator.isRunning()) {
121             // If we are still animating the fade into pip, then just move the surface and ensure
122             // we update with the new destination bounds, but don't interrupt the existing animation
123             // with a new bounds
124             mCurrentAnimator.setDestinationBounds(endBounds);
125         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS
126                 && mCurrentAnimator.isRunning()) {
127             mCurrentAnimator.setDestinationBounds(endBounds);
128             // construct new Rect instances in case they are recycled
129             mCurrentAnimator.updateEndValue(new Rect(endBounds));
130         } else {
131             mCurrentAnimator.cancel();
132             mCurrentAnimator = setupPipTransitionAnimator(
133                     PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
134         }
135         return mCurrentAnimator;
136     }
137 
getCurrentAnimator()138     PipTransitionAnimator getCurrentAnimator() {
139         return mCurrentAnimator;
140     }
141 
setupPipTransitionAnimator(PipTransitionAnimator animator)142     private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
143         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
144         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
145         animator.setFloatValues(FRACTION_START, FRACTION_END);
146         animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
147         return animator;
148     }
149 
150     /**
151      * Additional callback interface for PiP animation
152      */
153     public static class PipAnimationCallback {
154         /**
155          * Called when PiP animation is started.
156          */
onPipAnimationStart(PipTransitionAnimator animator)157         public void onPipAnimationStart(PipTransitionAnimator animator) {}
158 
159         /**
160          * Called when PiP animation is ended.
161          */
onPipAnimationEnd(SurfaceControl.Transaction tx, PipTransitionAnimator animator)162         public void onPipAnimationEnd(SurfaceControl.Transaction tx,
163                 PipTransitionAnimator animator) {}
164 
165         /**
166          * Called when PiP animation is cancelled.
167          */
onPipAnimationCancel(PipTransitionAnimator animator)168         public void onPipAnimationCancel(PipTransitionAnimator animator) {}
169     }
170 
171     /**
172      * Animator for PiP transition animation which supports both alpha and bounds animation.
173      * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
174      */
175     public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
176             ValueAnimator.AnimatorUpdateListener,
177             ValueAnimator.AnimatorListener {
178         private final SurfaceControl mLeash;
179         private final @AnimationType int mAnimationType;
180         private final Rect mDestinationBounds = new Rect();
181 
182         protected T mCurrentValue;
183         protected T mStartValue;
184         private T mEndValue;
185         private PipAnimationCallback mPipAnimationCallback;
186         private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
187                 mSurfaceControlTransactionFactory;
188         private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
189         private @TransitionDirection int mTransitionDirection;
190 
PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T startValue, T endValue)191         private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
192                 Rect destinationBounds, T startValue, T endValue) {
193             mLeash = leash;
194             mAnimationType = animationType;
195             mDestinationBounds.set(destinationBounds);
196             mStartValue = startValue;
197             mEndValue = endValue;
198             addListener(this);
199             addUpdateListener(this);
200             mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
201             mTransitionDirection = TRANSITION_DIRECTION_NONE;
202         }
203 
204         @Override
onAnimationStart(Animator animation)205         public void onAnimationStart(Animator animation) {
206             mCurrentValue = mStartValue;
207             onStartTransaction(mLeash, newSurfaceControlTransaction());
208             if (mPipAnimationCallback != null) {
209                 mPipAnimationCallback.onPipAnimationStart(this);
210             }
211         }
212 
213         @Override
onAnimationUpdate(ValueAnimator animation)214         public void onAnimationUpdate(ValueAnimator animation) {
215             applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
216                     animation.getAnimatedFraction());
217         }
218 
219         @Override
onAnimationEnd(Animator animation)220         public void onAnimationEnd(Animator animation) {
221             mCurrentValue = mEndValue;
222             final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
223             onEndTransaction(mLeash, tx);
224             if (mPipAnimationCallback != null) {
225                 mPipAnimationCallback.onPipAnimationEnd(tx, this);
226             }
227         }
228 
229         @Override
onAnimationCancel(Animator animation)230         public void onAnimationCancel(Animator animation) {
231             if (mPipAnimationCallback != null) {
232                 mPipAnimationCallback.onPipAnimationCancel(this);
233             }
234         }
235 
onAnimationRepeat(Animator animation)236         @Override public void onAnimationRepeat(Animator animation) {}
237 
getAnimationType()238         @AnimationType int getAnimationType() {
239             return mAnimationType;
240         }
241 
setPipAnimationCallback(PipAnimationCallback callback)242         PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
243             mPipAnimationCallback = callback;
244             return this;
245         }
246 
getTransitionDirection()247         @TransitionDirection int getTransitionDirection() {
248             return mTransitionDirection;
249         }
250 
setTransitionDirection(@ransitionDirection int direction)251         PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
252             if (direction != TRANSITION_DIRECTION_SAME) {
253                 mTransitionDirection = direction;
254             }
255             return this;
256         }
257 
getStartValue()258         T getStartValue() {
259             return mStartValue;
260         }
261 
getEndValue()262         T getEndValue() {
263             return mEndValue;
264         }
265 
getDestinationBounds()266         Rect getDestinationBounds() {
267             return mDestinationBounds;
268         }
269 
setDestinationBounds(Rect destinationBounds)270         void setDestinationBounds(Rect destinationBounds) {
271             mDestinationBounds.set(destinationBounds);
272             if (mAnimationType == ANIM_TYPE_ALPHA) {
273                 onStartTransaction(mLeash, newSurfaceControlTransaction());
274             }
275         }
276 
setCurrentValue(T value)277         void setCurrentValue(T value) {
278             mCurrentValue = value;
279         }
280 
shouldApplyCornerRadius()281         boolean shouldApplyCornerRadius() {
282             return !isOutPipDirection(mTransitionDirection);
283         }
284 
inScaleTransition()285         boolean inScaleTransition() {
286             if (mAnimationType != ANIM_TYPE_BOUNDS) return false;
287             return !isInPipDirection(getTransitionDirection());
288         }
289 
290         /**
291          * Updates the {@link #mEndValue}.
292          *
293          * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation.
294          * This is typically used when we receive a shelf height adjustment during the bounds
295          * animation. In which case we can update the end bounds and keep the existing animation
296          * running instead of cancelling it.
297          */
updateEndValue(T endValue)298         void updateEndValue(T endValue) {
299             mEndValue = endValue;
300         }
301 
newSurfaceControlTransaction()302         SurfaceControl.Transaction newSurfaceControlTransaction() {
303             return mSurfaceControlTransactionFactory.getTransaction();
304         }
305 
306         @VisibleForTesting
setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)307         void setSurfaceControlTransactionFactory(
308                 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
309             mSurfaceControlTransactionFactory = factory;
310         }
311 
getSurfaceTransactionHelper()312         PipSurfaceTransactionHelper getSurfaceTransactionHelper() {
313             return mSurfaceTransactionHelper;
314         }
315 
setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper)316         void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) {
317             mSurfaceTransactionHelper = helper;
318         }
319 
onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)320         void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
321 
onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)322         void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
323 
applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)324         abstract void applySurfaceControlTransaction(SurfaceControl leash,
325                 SurfaceControl.Transaction tx, float fraction);
326 
ofAlpha(SurfaceControl leash, Rect destinationBounds, float startValue, float endValue)327         static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
328                 Rect destinationBounds, float startValue, float endValue) {
329             return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
330                     destinationBounds, startValue, endValue) {
331                 @Override
332                 void applySurfaceControlTransaction(SurfaceControl leash,
333                         SurfaceControl.Transaction tx, float fraction) {
334                     final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
335                     setCurrentValue(alpha);
336                     getSurfaceTransactionHelper().alpha(tx, leash, alpha);
337                     tx.apply();
338                 }
339 
340                 @Override
341                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
342                     if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) {
343                         // while removing the pip stack, no extra work needs to be done here.
344                         return;
345                     }
346                     getSurfaceTransactionHelper()
347                             .resetScale(tx, leash, getDestinationBounds())
348                             .crop(tx, leash, getDestinationBounds())
349                             .round(tx, leash, shouldApplyCornerRadius());
350                     tx.show(leash);
351                     tx.apply();
352                 }
353 
354                 @Override
355                 void updateEndValue(Float endValue) {
356                     super.updateEndValue(endValue);
357                     mStartValue = mCurrentValue;
358                 }
359             };
360         }
361 
ofBounds(SurfaceControl leash, Rect startValue, Rect endValue, Rect sourceHintRect)362         static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
363                 Rect startValue, Rect endValue, Rect sourceHintRect) {
364             // Just for simplicity we'll interpolate between the source rect hint insets and empty
365             // insets to calculate the window crop
366             final Rect initialStartValue = new Rect(startValue);
367             final Rect sourceHintRectInsets = sourceHintRect != null
368                     ? new Rect(sourceHintRect.left - startValue.left,
369                             sourceHintRect.top - startValue.top,
370                             startValue.right - sourceHintRect.right,
371                             startValue.bottom - sourceHintRect.bottom)
372                     : null;
373             final Rect sourceInsets = new Rect(0, 0, 0, 0);
374 
375             // construct new Rect instances in case they are recycled
376             return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
377                     endValue, new Rect(startValue), new Rect(endValue)) {
378                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
379                 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
380 
381                 @Override
382                 void applySurfaceControlTransaction(SurfaceControl leash,
383                         SurfaceControl.Transaction tx, float fraction) {
384                     final Rect start = getStartValue();
385                     final Rect end = getEndValue();
386                     Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
387                     setCurrentValue(bounds);
388                     if (inScaleTransition()) {
389                         if (isOutPipDirection(getTransitionDirection())) {
390                             getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
391                         } else {
392                             getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
393                         }
394                     } else {
395                         if (sourceHintRectInsets != null) {
396                             Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
397                                     sourceHintRectInsets);
398                             getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue,
399                                     bounds, insets);
400                         } else {
401                             getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
402                         }
403                     }
404                     tx.apply();
405                 }
406 
407                 @Override
408                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
409                     getSurfaceTransactionHelper()
410                             .alpha(tx, leash, 1f)
411                             .round(tx, leash, shouldApplyCornerRadius());
412                     tx.show(leash);
413                     tx.apply();
414                 }
415 
416                 @Override
417                 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
418                     // NOTE: intentionally does not apply the transaction here.
419                     // this end transaction should get executed synchronously with the final
420                     // WindowContainerTransaction in task organizer
421                     getSurfaceTransactionHelper()
422                             .resetScale(tx, leash, getDestinationBounds())
423                             .crop(tx, leash, getDestinationBounds());
424                 }
425 
426                 @Override
427                 void updateEndValue(Rect endValue) {
428                     super.updateEndValue(endValue);
429                     if (mStartValue != null && mCurrentValue != null) {
430                         mStartValue.set(mCurrentValue);
431                     }
432                 }
433             };
434         }
435     }
436 }
437