blob: b5fd406de36832b61d3f0388158243fc01e0cb20 [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;
22import android.annotation.MainThread;
23import android.content.Context;
24import android.graphics.Rect;
25import android.os.RemoteException;
26import android.view.IWindowContainer;
27import android.view.SurfaceControl;
28import android.view.animation.AnimationUtils;
29import android.view.animation.Interpolator;
30
31import com.android.internal.annotations.VisibleForTesting;
32
33import java.lang.annotation.Retention;
34import java.lang.annotation.RetentionPolicy;
35
36/**
37 * Controller class of PiP animations (both from and to PiP mode).
38 */
39public class PipAnimationController {
40 private static final float FRACTION_START = 0f;
41 private static final float FRACTION_END = 1f;
42
43 public static final int DURATION_NONE = 0;
44 public static final int DURATION_DEFAULT_MS = 425;
45 public static final int ANIM_TYPE_BOUNDS = 0;
46 public static final int ANIM_TYPE_ALPHA = 1;
47
48 @IntDef(prefix = { "ANIM_TYPE_" }, value = {
49 ANIM_TYPE_BOUNDS,
50 ANIM_TYPE_ALPHA
51 })
52 @Retention(RetentionPolicy.SOURCE)
53 public @interface AnimationType {}
54
55 private final Interpolator mFastOutSlowInInterpolator;
56
57 private PipTransitionAnimator mCurrentAnimator;
58
59 PipAnimationController(Context context) {
60 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
61 com.android.internal.R.interpolator.fast_out_slow_in);
62 }
63
64 @MainThread
65 PipTransitionAnimator getAnimator(IWindowContainer wc, boolean scheduleFinishPip,
66 Rect destinationBounds, float alphaStart, float alphaEnd) {
67 if (mCurrentAnimator == null) {
68 mCurrentAnimator = setupPipTransitionAnimator(
69 PipTransitionAnimator.ofAlpha(wc, scheduleFinishPip,
70 destinationBounds, alphaStart, alphaEnd));
71 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
72 && mCurrentAnimator.isRunning()) {
73 mCurrentAnimator.updateEndValue(alphaEnd);
74 } else {
75 mCurrentAnimator.cancel();
76 mCurrentAnimator = setupPipTransitionAnimator(
77 PipTransitionAnimator.ofAlpha(wc, scheduleFinishPip,
78 destinationBounds, alphaStart, alphaEnd));
79 }
80 return mCurrentAnimator;
81 }
82
83 @MainThread
84 PipTransitionAnimator getAnimator(IWindowContainer wc, boolean scheduleFinishPip,
85 Rect startBounds, Rect endBounds) {
86 if (mCurrentAnimator == null) {
87 mCurrentAnimator = setupPipTransitionAnimator(
88 PipTransitionAnimator.ofBounds(wc, scheduleFinishPip, startBounds, endBounds));
89 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS
90 && mCurrentAnimator.isRunning()) {
91 mCurrentAnimator.setDestinationBounds(endBounds);
92 // construct new Rect instances in case they are recycled
93 mCurrentAnimator.updateEndValue(new Rect(endBounds));
94 } else {
95 mCurrentAnimator.cancel();
96 mCurrentAnimator = setupPipTransitionAnimator(
97 PipTransitionAnimator.ofBounds(wc, scheduleFinishPip, startBounds, endBounds));
98 }
99 return mCurrentAnimator;
100 }
101
102 private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
103 animator.setInterpolator(mFastOutSlowInInterpolator);
104 animator.setFloatValues(FRACTION_START, FRACTION_END);
105 return animator;
106 }
107
108 /**
109 * Additional callback interface for PiP animation
110 */
111 public static class PipAnimationCallback {
112 /**
113 * Called when PiP animation is started.
114 */
115 public void onPipAnimationStart(IWindowContainer wc, PipTransitionAnimator animator) {}
116
117 /**
118 * Called when PiP animation is ended.
119 */
120 public void onPipAnimationEnd(IWindowContainer wc, SurfaceControl.Transaction tx,
121 PipTransitionAnimator animator) {}
122
123 /**
124 * Called when PiP animation is cancelled.
125 */
126 public void onPipAnimationCancel(IWindowContainer wc, PipTransitionAnimator animator) {}
127 }
128
129 /**
130 * Animator for PiP transition animation which supports both alpha and bounds animation.
131 * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
132 */
133 public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
134 ValueAnimator.AnimatorUpdateListener,
135 ValueAnimator.AnimatorListener {
136 private final IWindowContainer mWindowContainer;
137 private final boolean mScheduleFinishPip;
138 private final SurfaceControl mLeash;
139 private final @AnimationType int mAnimationType;
140 private final Rect mDestinationBounds = new Rect();
141
142 private T mStartValue;
143 private T mEndValue;
144 private T mCurrentValue;
145 private PipAnimationCallback mPipAnimationCallback;
146 private SurfaceControlTransactionFactory mSurfaceControlTransactionFactory;
147
148 private PipTransitionAnimator(IWindowContainer wc, boolean scheduleFinishPip,
149 @AnimationType int animationType, Rect destinationBounds,
150 T startValue, T endValue) {
151 mWindowContainer = wc;
152 mScheduleFinishPip = scheduleFinishPip;
153 try {
154 mLeash = wc.getLeash();
155 mAnimationType = animationType;
156 mDestinationBounds.set(destinationBounds);
157 mStartValue = startValue;
158 mEndValue = endValue;
159 addListener(this);
160 addUpdateListener(this);
161 mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
162 } catch (RemoteException e) {
163 throw new RuntimeException(e);
164 }
165 }
166
167 @Override
168 public void onAnimationStart(Animator animation) {
169 mCurrentValue = mStartValue;
170 applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(), FRACTION_START);
171 if (mPipAnimationCallback != null) {
172 mPipAnimationCallback.onPipAnimationStart(mWindowContainer, this);
173 }
174 }
175
176 @Override
177 public void onAnimationUpdate(ValueAnimator animation) {
178 applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
179 animation.getAnimatedFraction());
180 }
181
182 @Override
183 public void onAnimationEnd(Animator animation) {
184 mCurrentValue = mEndValue;
185 final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
186 applySurfaceControlTransaction(mLeash, tx, FRACTION_END);
187 if (mPipAnimationCallback != null) {
188 mPipAnimationCallback.onPipAnimationEnd(mWindowContainer, tx, this);
189 }
190 }
191
192 @Override
193 public void onAnimationCancel(Animator animation) {
194 if (mPipAnimationCallback != null) {
195 mPipAnimationCallback.onPipAnimationCancel(mWindowContainer, this);
196 }
197 }
198
199 @Override public void onAnimationRepeat(Animator animation) {}
200
201 @AnimationType int getAnimationType() {
202 return mAnimationType;
203 }
204
205 PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
206 mPipAnimationCallback = callback;
207 return this;
208 }
209
210 boolean shouldScheduleFinishPip() {
211 return mScheduleFinishPip;
212 }
213
214 T getStartValue() {
215 return mStartValue;
216 }
217
218 T getEndValue() {
219 return mEndValue;
220 }
221
222 Rect getDestinationBounds() {
223 return mDestinationBounds;
224 }
225
226 void setDestinationBounds(Rect destinationBounds) {
227 mDestinationBounds.set(destinationBounds);
228 }
229
230 void setCurrentValue(T value) {
231 mCurrentValue = value;
232 }
233
234 /**
235 * Updates the {@link #mEndValue}.
236 *
237 * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation.
238 * This is typically used when we receive a shelf height adjustment during the bounds
239 * animation. In which case we can update the end bounds and keep the existing animation
240 * running instead of cancelling it.
241 */
242 void updateEndValue(T endValue) {
243 mEndValue = endValue;
244 mStartValue = mCurrentValue;
245 }
246
247 SurfaceControl.Transaction newSurfaceControlTransaction() {
248 return mSurfaceControlTransactionFactory.getTransaction();
249 }
250
251 @VisibleForTesting
252 void setSurfaceControlTransactionFactory(SurfaceControlTransactionFactory factory) {
253 mSurfaceControlTransactionFactory = factory;
254 }
255
256 abstract void applySurfaceControlTransaction(SurfaceControl leash,
257 SurfaceControl.Transaction tx, float fraction);
258
259 static PipTransitionAnimator<Float> ofAlpha(IWindowContainer wc, boolean scheduleFinishPip,
260 Rect destinationBounds, float startValue, float endValue) {
261 return new PipTransitionAnimator<Float>(wc, scheduleFinishPip, ANIM_TYPE_ALPHA,
262 destinationBounds, startValue, endValue) {
263 @Override
264 void applySurfaceControlTransaction(SurfaceControl leash,
265 SurfaceControl.Transaction tx, float fraction) {
266 final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction;
267 setCurrentValue(alpha);
268 tx.setAlpha(leash, alpha);
269 if (Float.compare(fraction, FRACTION_START) == 0) {
270 // Ensure the start condition
271 final Rect bounds = getDestinationBounds();
272 tx.setPosition(leash, bounds.left, bounds.top)
273 .setWindowCrop(leash, bounds.width(), bounds.height());
274 }
275 tx.apply();
276 }
277 };
278 }
279
280 static PipTransitionAnimator<Rect> ofBounds(IWindowContainer wc, boolean scheduleFinishPip,
281 Rect startValue, Rect endValue) {
282 // construct new Rect instances in case they are recycled
283 return new PipTransitionAnimator<Rect>(wc, scheduleFinishPip, ANIM_TYPE_BOUNDS,
284 endValue, new Rect(startValue), new Rect(endValue)) {
285 private final Rect mTmpRect = new Rect();
286
287 private int getCastedFractionValue(float start, float end, float fraction) {
288 return (int) (start * (1 - fraction) + end * fraction + .5f);
289 }
290
291 @Override
292 void applySurfaceControlTransaction(SurfaceControl leash,
293 SurfaceControl.Transaction tx, float fraction) {
294 final Rect start = getStartValue();
295 final Rect end = getEndValue();
296 mTmpRect.set(
297 getCastedFractionValue(start.left, end.left, fraction),
298 getCastedFractionValue(start.top, end.top, fraction),
299 getCastedFractionValue(start.right, end.right, fraction),
300 getCastedFractionValue(start.bottom, end.bottom, fraction));
301 setCurrentValue(mTmpRect);
302 tx.setPosition(leash, mTmpRect.left, mTmpRect.top)
303 .setWindowCrop(leash, mTmpRect.width(), mTmpRect.height());
304 if (Float.compare(fraction, FRACTION_START) == 0) {
305 // Ensure the start condition
306 tx.setAlpha(leash, 1f);
307 }
308 tx.apply();
309 }
310 };
311 }
312 }
313
314 interface SurfaceControlTransactionFactory {
315 SurfaceControl.Transaction getTransaction();
316 }
317}