blob: 67802bc9888d3ef0f5b4977a985ec15fd99a4bbb [file] [log] [blame]
Hongwei Wang85cf41f2020-01-15 15:14:47 -08001/*
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
17package com.android.systemui.pip;
18
19import android.animation.Animator;
20import android.animation.ValueAnimator;
21import android.annotation.IntDef;
Hongwei Wang85cf41f2020-01-15 15:14:47 -080022import android.content.Context;
23import android.graphics.Rect;
Hongwei Wang85cf41f2020-01-15 15:14:47 -080024import android.view.SurfaceControl;
25import android.view.animation.AnimationUtils;
26import android.view.animation.Interpolator;
27
28import com.android.internal.annotations.VisibleForTesting;
29
30import java.lang.annotation.Retention;
31import java.lang.annotation.RetentionPolicy;
32
33/**
34 * Controller class of PiP animations (both from and to PiP mode).
35 */
36public class PipAnimationController {
37 private static final float FRACTION_START = 0f;
38 private static final float FRACTION_END = 1f;
39
Hongwei Wang85cf41f2020-01-15 15:14:47 -080040 public static final int DURATION_DEFAULT_MS = 425;
41 public static final int ANIM_TYPE_BOUNDS = 0;
42 public static final int ANIM_TYPE_ALPHA = 1;
43
44 @IntDef(prefix = { "ANIM_TYPE_" }, value = {
45 ANIM_TYPE_BOUNDS,
46 ANIM_TYPE_ALPHA
47 })
48 @Retention(RetentionPolicy.SOURCE)
49 public @interface AnimationType {}
50
Hongwei Wangdf8bb002020-03-03 17:41:02 -080051 static final int TRANSITION_DIRECTION_NONE = 0;
52 static final int TRANSITION_DIRECTION_SAME = 1;
53 static final int TRANSITION_DIRECTION_TO_PIP = 2;
54 static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3;
55
56 @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
57 TRANSITION_DIRECTION_NONE,
58 TRANSITION_DIRECTION_SAME,
59 TRANSITION_DIRECTION_TO_PIP,
60 TRANSITION_DIRECTION_TO_FULLSCREEN
61 })
62 @Retention(RetentionPolicy.SOURCE)
63 @interface TransitionDirection {}
64
Hongwei Wang85cf41f2020-01-15 15:14:47 -080065 private final Interpolator mFastOutSlowInInterpolator;
66
67 private PipTransitionAnimator mCurrentAnimator;
68
69 PipAnimationController(Context context) {
70 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
71 com.android.internal.R.interpolator.fast_out_slow_in);
72 }
73
Hongwei Wangdf8bb002020-03-03 17:41:02 -080074 @SuppressWarnings("unchecked")
75 PipTransitionAnimator getAnimator(SurfaceControl leash,
Hongwei Wang85cf41f2020-01-15 15:14:47 -080076 Rect destinationBounds, float alphaStart, float alphaEnd) {
77 if (mCurrentAnimator == null) {
78 mCurrentAnimator = setupPipTransitionAnimator(
Hongwei Wangdf8bb002020-03-03 17:41:02 -080079 PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
Hongwei Wang85cf41f2020-01-15 15:14:47 -080080 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
81 && mCurrentAnimator.isRunning()) {
82 mCurrentAnimator.updateEndValue(alphaEnd);
83 } else {
84 mCurrentAnimator.cancel();
85 mCurrentAnimator = setupPipTransitionAnimator(
Hongwei Wangdf8bb002020-03-03 17:41:02 -080086 PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
Hongwei Wang85cf41f2020-01-15 15:14:47 -080087 }
88 return mCurrentAnimator;
89 }
90
Hongwei Wangdf8bb002020-03-03 17:41:02 -080091 @SuppressWarnings("unchecked")
92 PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) {
Hongwei Wang85cf41f2020-01-15 15:14:47 -080093 if (mCurrentAnimator == null) {
94 mCurrentAnimator = setupPipTransitionAnimator(
Hongwei Wangdf8bb002020-03-03 17:41:02 -080095 PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
Hongwei Wang85cf41f2020-01-15 15:14:47 -080096 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS
97 && mCurrentAnimator.isRunning()) {
98 mCurrentAnimator.setDestinationBounds(endBounds);
99 // construct new Rect instances in case they are recycled
100 mCurrentAnimator.updateEndValue(new Rect(endBounds));
101 } else {
102 mCurrentAnimator.cancel();
103 mCurrentAnimator = setupPipTransitionAnimator(
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800104 PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800105 }
106 return mCurrentAnimator;
107 }
108
Hongwei Wangea2ae852020-02-27 13:47:52 -0800109 PipTransitionAnimator getCurrentAnimator() {
110 return mCurrentAnimator;
111 }
112
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800113 private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
114 animator.setInterpolator(mFastOutSlowInInterpolator);
115 animator.setFloatValues(FRACTION_START, FRACTION_END);
116 return animator;
117 }
118
119 /**
120 * Additional callback interface for PiP animation
121 */
122 public static class PipAnimationCallback {
123 /**
124 * Called when PiP animation is started.
125 */
Winson Chung55701472020-03-04 19:30:30 -0800126 public void onPipAnimationStart(PipTransitionAnimator animator) {}
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800127
128 /**
129 * Called when PiP animation is ended.
130 */
Winson Chung55701472020-03-04 19:30:30 -0800131 public void onPipAnimationEnd(SurfaceControl.Transaction tx,
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800132 PipTransitionAnimator animator) {}
133
134 /**
135 * Called when PiP animation is cancelled.
136 */
Winson Chung55701472020-03-04 19:30:30 -0800137 public void onPipAnimationCancel(PipTransitionAnimator animator) {}
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800138 }
139
140 /**
141 * Animator for PiP transition animation which supports both alpha and bounds animation.
142 * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
143 */
144 public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
145 ValueAnimator.AnimatorUpdateListener,
146 ValueAnimator.AnimatorListener {
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800147 private final SurfaceControl mLeash;
148 private final @AnimationType int mAnimationType;
149 private final Rect mDestinationBounds = new Rect();
150
151 private T mStartValue;
152 private T mEndValue;
153 private T mCurrentValue;
154 private PipAnimationCallback mPipAnimationCallback;
155 private SurfaceControlTransactionFactory mSurfaceControlTransactionFactory;
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800156 private @TransitionDirection int mTransitionDirection;
157 private int mCornerRadius;
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800158
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800159 private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
160 Rect destinationBounds, T startValue, T endValue) {
Winson Chung55701472020-03-04 19:30:30 -0800161 mLeash = leash;
162 mAnimationType = animationType;
163 mDestinationBounds.set(destinationBounds);
164 mStartValue = startValue;
165 mEndValue = endValue;
166 addListener(this);
167 addUpdateListener(this);
168 mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800169 mTransitionDirection = TRANSITION_DIRECTION_NONE;
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800170 }
171
172 @Override
173 public void onAnimationStart(Animator animation) {
174 mCurrentValue = mStartValue;
175 applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(), FRACTION_START);
176 if (mPipAnimationCallback != null) {
Winson Chung55701472020-03-04 19:30:30 -0800177 mPipAnimationCallback.onPipAnimationStart(this);
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800178 }
179 }
180
181 @Override
182 public void onAnimationUpdate(ValueAnimator animation) {
183 applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
184 animation.getAnimatedFraction());
185 }
186
187 @Override
188 public void onAnimationEnd(Animator animation) {
189 mCurrentValue = mEndValue;
190 final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
191 applySurfaceControlTransaction(mLeash, tx, FRACTION_END);
192 if (mPipAnimationCallback != null) {
Winson Chung55701472020-03-04 19:30:30 -0800193 mPipAnimationCallback.onPipAnimationEnd(tx, this);
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800194 }
195 }
196
197 @Override
198 public void onAnimationCancel(Animator animation) {
199 if (mPipAnimationCallback != null) {
Winson Chung55701472020-03-04 19:30:30 -0800200 mPipAnimationCallback.onPipAnimationCancel(this);
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800201 }
202 }
203
204 @Override public void onAnimationRepeat(Animator animation) {}
205
206 @AnimationType int getAnimationType() {
207 return mAnimationType;
208 }
209
210 PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
211 mPipAnimationCallback = callback;
212 return this;
213 }
214
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800215 @TransitionDirection int getTransitionDirection() {
216 return mTransitionDirection;
217 }
218
219 PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
220 if (direction != TRANSITION_DIRECTION_SAME) {
221 mTransitionDirection = direction;
222 }
223 return this;
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800224 }
225
226 T getStartValue() {
227 return mStartValue;
228 }
229
230 T getEndValue() {
231 return mEndValue;
232 }
233
234 Rect getDestinationBounds() {
235 return mDestinationBounds;
236 }
237
238 void setDestinationBounds(Rect destinationBounds) {
239 mDestinationBounds.set(destinationBounds);
240 }
241
242 void setCurrentValue(T value) {
243 mCurrentValue = value;
244 }
245
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800246 int getCornerRadius() {
247 return mCornerRadius;
248 }
249
250 PipTransitionAnimator<T> setCornerRadius(int cornerRadius) {
251 mCornerRadius = cornerRadius;
252 return this;
253 }
254
255 boolean shouldApplyCornerRadius() {
256 return mTransitionDirection != TRANSITION_DIRECTION_TO_FULLSCREEN;
257 }
258
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800259 /**
260 * Updates the {@link #mEndValue}.
261 *
262 * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation.
263 * This is typically used when we receive a shelf height adjustment during the bounds
264 * animation. In which case we can update the end bounds and keep the existing animation
265 * running instead of cancelling it.
266 */
267 void updateEndValue(T endValue) {
268 mEndValue = endValue;
269 mStartValue = mCurrentValue;
270 }
271
272 SurfaceControl.Transaction newSurfaceControlTransaction() {
273 return mSurfaceControlTransactionFactory.getTransaction();
274 }
275
276 @VisibleForTesting
277 void setSurfaceControlTransactionFactory(SurfaceControlTransactionFactory factory) {
278 mSurfaceControlTransactionFactory = factory;
279 }
280
281 abstract void applySurfaceControlTransaction(SurfaceControl leash,
282 SurfaceControl.Transaction tx, float fraction);
283
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800284 static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800285 Rect destinationBounds, float startValue, float endValue) {
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800286 return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800287 destinationBounds, startValue, endValue) {
288 @Override
289 void applySurfaceControlTransaction(SurfaceControl leash,
290 SurfaceControl.Transaction tx, float fraction) {
291 final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
292 setCurrentValue(alpha);
293 tx.setAlpha(leash, alpha);
294 if (Float.compare(fraction, FRACTION_START) == 0) {
295 // Ensure the start condition
296 final Rect bounds = getDestinationBounds();
297 tx.setPosition(leash, bounds.left, bounds.top)
298 .setWindowCrop(leash, bounds.width(), bounds.height());
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800299 tx.setCornerRadius(leash,
300 shouldApplyCornerRadius() ? getCornerRadius() : 0);
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800301 }
302 tx.apply();
303 }
304 };
305 }
306
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800307 static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800308 Rect startValue, Rect endValue) {
309 // construct new Rect instances in case they are recycled
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800310 return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800311 endValue, new Rect(startValue), new Rect(endValue)) {
312 private final Rect mTmpRect = new Rect();
313
314 private int getCastedFractionValue(float start, float end, float fraction) {
315 return (int) (start * (1 - fraction) + end * fraction + .5f);
316 }
317
318 @Override
319 void applySurfaceControlTransaction(SurfaceControl leash,
320 SurfaceControl.Transaction tx, float fraction) {
321 final Rect start = getStartValue();
322 final Rect end = getEndValue();
323 mTmpRect.set(
324 getCastedFractionValue(start.left, end.left, fraction),
325 getCastedFractionValue(start.top, end.top, fraction),
326 getCastedFractionValue(start.right, end.right, fraction),
327 getCastedFractionValue(start.bottom, end.bottom, fraction));
328 setCurrentValue(mTmpRect);
329 tx.setPosition(leash, mTmpRect.left, mTmpRect.top)
330 .setWindowCrop(leash, mTmpRect.width(), mTmpRect.height());
331 if (Float.compare(fraction, FRACTION_START) == 0) {
332 // Ensure the start condition
333 tx.setAlpha(leash, 1f);
Hongwei Wangdf8bb002020-03-03 17:41:02 -0800334 tx.setCornerRadius(leash,
335 shouldApplyCornerRadius() ? getCornerRadius() : 0);
Hongwei Wang85cf41f2020-01-15 15:14:47 -0800336 }
337 tx.apply();
338 }
339 };
340 }
341 }
342
343 interface SurfaceControlTransactionFactory {
344 SurfaceControl.Transaction getTransaction();
345 }
346}