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