• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.server.wm;
18 
19 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
20 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
21 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
22 
23 import android.animation.AnimationHandler;
24 import android.animation.Animator;
25 import android.animation.ValueAnimator;
26 import android.annotation.IntDef;
27 import android.content.Context;
28 import android.graphics.Rect;
29 import android.os.Debug;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.util.ArrayMap;
33 import android.util.Slog;
34 import android.view.Choreographer;
35 import android.view.animation.AnimationUtils;
36 import android.view.animation.Interpolator;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 
44 /**
45  * Enables animating bounds of objects.
46  *
47  * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
48  * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
49  * relaunching it would cause poorer experience), these class provides a way to directly animate
50  * the bounds of the resized object.
51  *
52  * The object that is resized needs to implement {@link BoundsAnimationTarget} interface.
53  *
54  * NOTE: All calls to methods in this class should be done on the Animation thread
55  */
56 public class BoundsAnimationController {
57     private static final boolean DEBUG_LOCAL = false;
58     private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;
59     private static final String TAG = TAG_WITH_CLASS_NAME || DEBUG_LOCAL
60             ? "BoundsAnimationController" : TAG_WM;
61     private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
62 
63     private static final int DEFAULT_TRANSITION_DURATION = 425;
64 
65     @Retention(RetentionPolicy.SOURCE)
66     @IntDef({NO_PIP_MODE_CHANGED_CALLBACKS, SCHEDULE_PIP_MODE_CHANGED_ON_START,
67         SCHEDULE_PIP_MODE_CHANGED_ON_END})
68     public @interface SchedulePipModeChangedState {}
69     /** Do not schedule any PiP mode changed callbacks as a part of this animation. */
70     public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0;
71     /** Schedule a PiP mode changed callback when this animation starts. */
72     public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1;
73     /** Schedule a PiP mode changed callback when this animation ends. */
74     public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;
75 
76     public static final int BOUNDS = 0;
77     public static final int FADE_IN = 1;
78 
79     @IntDef({BOUNDS, FADE_IN}) public @interface AnimationType {}
80 
81     private static final int FADE_IN_DURATION = 500;
82 
83     // Only accessed on UI thread.
84     private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
85 
86     private final class AppTransitionNotifier
87             extends WindowManagerInternal.AppTransitionListener implements Runnable {
88 
onAppTransitionCancelledLocked()89         public void onAppTransitionCancelledLocked() {
90             if (DEBUG) Slog.d(TAG, "onAppTransitionCancelledLocked:"
91                     + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
92             animationFinished();
93         }
onAppTransitionFinishedLocked(IBinder token)94         public void onAppTransitionFinishedLocked(IBinder token) {
95             if (DEBUG) Slog.d(TAG, "onAppTransitionFinishedLocked:"
96                     + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
97             animationFinished();
98         }
animationFinished()99         private void animationFinished() {
100             if (mFinishAnimationAfterTransition) {
101                 mHandler.removeCallbacks(this);
102                 // This might end up calling into activity manager which will be bad since we have
103                 // the window manager lock held at this point. Post a message to take care of the
104                 // processing so we don't deadlock.
105                 mHandler.post(this);
106             }
107         }
108 
109         @Override
run()110         public void run() {
111             for (int i = 0; i < mRunningAnimations.size(); i++) {
112                 final BoundsAnimator b = mRunningAnimations.valueAt(i);
113                 b.onAnimationEnd(null);
114             }
115         }
116     }
117 
118     private final Handler mHandler;
119     private final AppTransition mAppTransition;
120     private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier();
121     private final Interpolator mFastOutSlowInInterpolator;
122     private boolean mFinishAnimationAfterTransition = false;
123     private final AnimationHandler mAnimationHandler;
124     private Choreographer mChoreographer;
125     private @AnimationType int mAnimationType;
126 
127     private static final int WAIT_FOR_DRAW_TIMEOUT_MS = 3000;
128 
BoundsAnimationController(Context context, AppTransition transition, Handler handler, AnimationHandler animationHandler)129     BoundsAnimationController(Context context, AppTransition transition, Handler handler,
130             AnimationHandler animationHandler) {
131         mHandler = handler;
132         mAppTransition = transition;
133         mAppTransition.registerListenerLocked(mAppTransitionNotifier);
134         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
135                 com.android.internal.R.interpolator.fast_out_slow_in);
136         mAnimationHandler = animationHandler;
137         if (animationHandler != null) {
138             // If an animation handler is provided, then ensure that it runs on the sf vsync tick
139             handler.post(() -> {
140                 mChoreographer = Choreographer.getSfInstance();
141                 animationHandler.setProvider(new SfVsyncFrameCallbackProvider(mChoreographer));
142             });
143         }
144     }
145 
146     @VisibleForTesting
147     final class BoundsAnimator extends ValueAnimator
148             implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
149 
150         private final BoundsAnimationTarget mTarget;
151         private final @AnimationType int mAnimationType;
152         private final Rect mFrom = new Rect();
153         private final Rect mTo = new Rect();
154         private final Rect mTmpRect = new Rect();
155         private final Rect mTmpTaskBounds = new Rect();
156 
157         // True if this this animation was canceled and will be replaced the another animation from
158         // the same {@link #BoundsAnimationTarget} target.
159         private boolean mSkipFinalResize;
160         // True if this animation was canceled by the user, not as a part of a replacing animation
161         private boolean mSkipAnimationEnd;
162 
163         // True if the animation target is animating from the fullscreen. Only one of
164         // {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be true at any time in the
165         // animation.
166         private boolean mMoveFromFullscreen;
167         // True if the animation target should be moved to the fullscreen stack at the end of this
168         // animation. Only one of {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be
169         // true at any time in the animation.
170         private boolean mMoveToFullscreen;
171 
172         // Whether to schedule PiP mode changes on animation start/end
173         private @SchedulePipModeChangedState int mSchedulePipModeChangedState;
174         private @SchedulePipModeChangedState int mPrevSchedulePipModeChangedState;
175 
176         // Depending on whether we are animating from
177         // a smaller to a larger size
178         private int mFrozenTaskWidth;
179         private int mFrozenTaskHeight;
180 
181         // Timeout callback to ensure we continue the animation if waiting for resuming or app
182         // windows drawn fails
183         private final Runnable mResumeRunnable = () -> {
184             if (DEBUG) Slog.d(TAG, "pause: timed out waiting for windows drawn");
185             resume();
186         };
187 
BoundsAnimator(BoundsAnimationTarget target, @AnimationType int animationType, Rect from, Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState, @SchedulePipModeChangedState int prevShedulePipModeChangedState, boolean moveFromFullscreen, boolean moveToFullscreen, Rect frozenTask)188         BoundsAnimator(BoundsAnimationTarget target, @AnimationType int animationType, Rect from,
189                 Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState,
190                 @SchedulePipModeChangedState int prevShedulePipModeChangedState,
191                 boolean moveFromFullscreen, boolean moveToFullscreen, Rect frozenTask) {
192             super();
193             mTarget = target;
194             mAnimationType = animationType;
195             mFrom.set(from);
196             mTo.set(to);
197             mSchedulePipModeChangedState = schedulePipModeChangedState;
198             mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState;
199             mMoveFromFullscreen = moveFromFullscreen;
200             mMoveToFullscreen = moveToFullscreen;
201             addUpdateListener(this);
202             addListener(this);
203 
204             // If we are animating from smaller to larger, we want to change the task bounds
205             // to their final size immediately so we can use scaling to make the window
206             // larger. Likewise if we are going from bigger to smaller, we want to wait until
207             // the end so we don't have to upscale from the smaller finished size.
208             if (mAnimationType == BOUNDS) {
209                 if (animatingToLargerSize()) {
210                     mFrozenTaskWidth = mTo.width();
211                     mFrozenTaskHeight = mTo.height();
212                 } else {
213                     mFrozenTaskWidth = frozenTask.isEmpty() ? mFrom.width() : frozenTask.width();
214                     mFrozenTaskHeight = frozenTask.isEmpty() ? mFrom.height() : frozenTask.height();
215                 }
216             }
217         }
218 
219         @Override
onAnimationStart(Animator animation)220         public void onAnimationStart(Animator animation) {
221             if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
222                     + " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState
223                     + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
224             mFinishAnimationAfterTransition = false;
225             mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
226                     mFrom.top + mFrozenTaskHeight);
227 
228             // Boost the thread priority of the animation thread while the bounds animation is
229             // running
230             updateBooster();
231 
232             // Ensure that we have prepared the target for animation before we trigger any size
233             // changes, so it can swap surfaces in to appropriate modes, or do as it wishes
234             // otherwise.
235             boolean continueAnimation;
236             if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
237                 continueAnimation = mTarget.onAnimationStart(
238                         mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START,
239                         false /* forceUpdate */, mAnimationType);
240 
241                 // When starting an animation from fullscreen, pause here and wait for the
242                 // windows-drawn signal before we start the rest of the transition down into PiP.
243                 if (continueAnimation && mMoveFromFullscreen
244                         && mTarget.shouldDeferStartOnMoveToFullscreen()) {
245                     pause();
246                 }
247             } else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
248                     mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
249                 // We are replacing a running animation into PiP, but since it hasn't completed, the
250                 // client will not currently receive any picture-in-picture mode change callbacks.
251                 // However, we still need to report to them that they are leaving PiP, so this will
252                 // force an update via a mode changed callback.
253                 continueAnimation = mTarget.onAnimationStart(
254                         true /* schedulePipModeChangedCallback */, true /* forceUpdate */,
255                         mAnimationType);
256             } else {
257                 // The animation is already running, but we should check that the TaskStack is still
258                 // valid before continuing with the animation
259                 continueAnimation = mTarget.isAttached();
260             }
261 
262             if (!continueAnimation) {
263                 // No point of trying to animate something that isn't attached to the hierarchy
264                 // anymore.
265                 cancel();
266                 return;
267             }
268 
269             // Immediately update the task bounds if they have to become larger, but preserve
270             // the starting position so we don't jump at the beginning of the animation.
271             if (animatingToLargerSize()) {
272                 mTarget.setPinnedStackSize(mFrom, mTmpRect);
273 
274                 // We pause the animation until the app has drawn at the new size.
275                 // The target will notify us via BoundsAnimationController#resume.
276                 // We do this here and pause the animation, rather than just defer starting it
277                 // so we can enter the animating state and have WindowStateAnimator apply the
278                 // correct logic to make this resize seamless.
279                 if (mMoveToFullscreen) {
280                     pause();
281                 }
282             }
283         }
284 
285         @Override
pause()286         public void pause() {
287             if (DEBUG) Slog.d(TAG, "pause: waiting for windows drawn");
288             super.pause();
289             mHandler.postDelayed(mResumeRunnable, WAIT_FOR_DRAW_TIMEOUT_MS);
290         }
291 
292         @Override
resume()293         public void resume() {
294             if (DEBUG) Slog.d(TAG, "resume:");
295             mHandler.removeCallbacks(mResumeRunnable);
296             super.resume();
297         }
298 
299         @Override
onAnimationUpdate(ValueAnimator animation)300         public void onAnimationUpdate(ValueAnimator animation) {
301             final float value = (Float) animation.getAnimatedValue();
302             if (mAnimationType == FADE_IN) {
303                 if (!mTarget.setPinnedStackAlpha(value)) {
304                     cancelAndCallAnimationEnd();
305                 }
306                 return;
307             }
308 
309             final float remains = 1 - value;
310             mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
311             mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
312             mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
313             mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
314             if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
315                     + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
316                     + " remains=" + remains);
317 
318             mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
319                     mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
320 
321             if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
322                 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
323                 // any further animation.
324                 if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
325 
326                 // If we have already scheduled a PiP mode changed at the start of the animation,
327                 // then we need to clean up and schedule one at the end, since we have canceled the
328                 // animation to the final state.
329                 if (mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
330                     mSchedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
331                 }
332 
333                 // Since we are cancelling immediately without a replacement animation, send the
334                 // animation end to maintain callback parity, but also skip any further resizes
335                 cancelAndCallAnimationEnd();
336             }
337         }
338 
339         @Override
onAnimationEnd(Animator animation)340         public void onAnimationEnd(Animator animation) {
341             if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
342                     + " mSkipFinalResize=" + mSkipFinalResize
343                     + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
344                     + " mAppTransitionIsRunning=" + mAppTransition.isRunning()
345                     + " callers=" + Debug.getCallers(2));
346 
347             // There could be another animation running. For example in the
348             // move to fullscreen case, recents will also be closing while the
349             // previous task will be taking its place in the fullscreen stack.
350             // we have to ensure this is completed before we finish the animation
351             // and take our place in the fullscreen stack.
352             if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
353                 mFinishAnimationAfterTransition = true;
354                 return;
355             }
356 
357             if (!mSkipAnimationEnd) {
358                 // If this animation has already scheduled the picture-in-picture mode on start, and
359                 // we are not skipping the final resize due to being canceled, then move the PiP to
360                 // fullscreen once the animation ends
361                 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
362                         + " moveToFullscreen=" + mMoveToFullscreen);
363                 mTarget.onAnimationEnd(mSchedulePipModeChangedState ==
364                         SCHEDULE_PIP_MODE_CHANGED_ON_END, !mSkipFinalResize ? mTo : null,
365                                 mMoveToFullscreen);
366             }
367 
368             // Clean up this animation
369             removeListener(this);
370             removeUpdateListener(this);
371             mRunningAnimations.remove(mTarget);
372 
373             // Reset the thread priority of the animation thread after the bounds animation is done
374             updateBooster();
375         }
376 
377         @Override
onAnimationCancel(Animator animation)378         public void onAnimationCancel(Animator animation) {
379             // Always skip the final resize when the animation is canceled
380             mSkipFinalResize = true;
381             mMoveToFullscreen = false;
382         }
383 
cancelAndCallAnimationEnd()384         private void cancelAndCallAnimationEnd() {
385             if (DEBUG) Slog.d(TAG, "cancelAndCallAnimationEnd: mTarget=" + mTarget);
386             mSkipAnimationEnd = false;
387             super.cancel();
388         }
389 
390         @Override
cancel()391         public void cancel() {
392             if (DEBUG) Slog.d(TAG, "cancel: mTarget=" + mTarget);
393             mSkipAnimationEnd = true;
394             super.cancel();
395 
396             // Reset the thread priority of the animation thread if the bounds animation is canceled
397             updateBooster();
398         }
399 
400         /**
401          * @return true if the animation target is the same as the input bounds.
402          */
isAnimatingTo(Rect bounds)403         boolean isAnimatingTo(Rect bounds) {
404             return mTo.equals(bounds);
405         }
406 
407         /**
408          * @return true if we are animating to a larger surface size
409          */
410         @VisibleForTesting
animatingToLargerSize()411         boolean animatingToLargerSize() {
412             // TODO: Fix this check for aspect ratio changes
413             return (mFrom.width() * mFrom.height() < mTo.width() * mTo.height());
414         }
415 
416         @Override
onAnimationRepeat(Animator animation)417         public void onAnimationRepeat(Animator animation) {
418             // Do nothing
419         }
420 
421         @Override
getAnimationHandler()422         public AnimationHandler getAnimationHandler() {
423             if (mAnimationHandler != null) {
424                 return mAnimationHandler;
425             }
426             return super.getAnimationHandler();
427         }
428     }
429 
animateBounds(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveFromFullscreen, boolean moveToFullscreen, @AnimationType int animationType)430     public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to,
431             int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
432             boolean moveFromFullscreen, boolean moveToFullscreen,
433             @AnimationType int animationType) {
434         animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState,
435                 moveFromFullscreen, moveToFullscreen, animationType);
436     }
437 
438     /**
439      * Cancel existing animation if the destination was modified.
440      */
cancel(final BoundsAnimationTarget target)441     void cancel(final BoundsAnimationTarget target) {
442         final BoundsAnimator existing = mRunningAnimations.get(target);
443         if (existing != null) {
444             // Cancel animation. Since its already started, send animation end to client.
445             if (DEBUG) Slog.d(TAG, "cancel: mTarget= " + target);
446             existing.cancelAndCallAnimationEnd();
447         }
448     }
449 
450     @VisibleForTesting
animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveFromFullscreen, boolean moveToFullscreen, @AnimationType int animationType)451     BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
452             int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
453             boolean moveFromFullscreen, boolean moveToFullscreen,
454             @AnimationType int animationType) {
455         final BoundsAnimator existing = mRunningAnimations.get(target);
456 
457         if (isRunningFadeInAnimation(target) && from.width() == to.width()
458                 && from.height() == to.height()) {
459             animationType = FADE_IN;
460         }
461         final boolean replacing = existing != null;
462         @SchedulePipModeChangedState int prevSchedulePipModeChangedState =
463                 NO_PIP_MODE_CHANGED_CALLBACKS;
464 
465         if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
466                 + " schedulePipModeChangedState=" + schedulePipModeChangedState
467                 + " replacing=" + replacing);
468 
469         Rect frozenTask = new Rect();
470         if (replacing) {
471             if (existing.isAnimatingTo(to) && (!moveToFullscreen || existing.mMoveToFullscreen)
472                     && (!moveFromFullscreen || existing.mMoveFromFullscreen)) {
473                 // Just let the current animation complete if it has the same destination as the
474                 // one we are trying to start, and, if moveTo/FromFullscreen was requested, already
475                 // has that flag set.
476                 if (DEBUG) Slog.d(TAG, "animateBounds: same destination and moveTo/From flags as "
477                         + "existing=" + existing + ", ignoring...");
478                 return existing;
479             }
480 
481             // Save the previous state
482             prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState;
483 
484             // Update the PiP callback states if we are replacing the animation
485             if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
486                 if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
487                     if (DEBUG) Slog.d(TAG, "animateBounds: still animating to fullscreen, keep"
488                             + " existing deferred state");
489                 } else {
490                     if (DEBUG) Slog.d(TAG, "animateBounds: fullscreen animation canceled, callback"
491                             + " on start already processed, schedule deferred update on end");
492                     schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
493                 }
494             } else if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END) {
495                 if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
496                     if (DEBUG) Slog.d(TAG, "animateBounds: non-fullscreen animation canceled,"
497                             + " callback on start will be processed");
498                 } else {
499                     if (DEBUG) Slog.d(TAG, "animateBounds: still animating from fullscreen, keep"
500                             + " existing deferred state");
501                     schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
502                 }
503             }
504 
505             // We need to keep the previous moveTo/FromFullscreen flag, unless the new animation
506             // specifies a direction.
507             if (!moveFromFullscreen && !moveToFullscreen) {
508                 moveToFullscreen = existing.mMoveToFullscreen;
509                 moveFromFullscreen = existing.mMoveFromFullscreen;
510             }
511 
512             // We are in the middle of an existing animation, so that this new animation may
513             // start from an interpolated bounds. We should keep using the existing frozen task
514             // width/height for consistent configurations.
515             frozenTask.set(0, 0, existing.mFrozenTaskWidth, existing.mFrozenTaskHeight);
516 
517             // Since we are replacing, we skip both animation start and end callbacks
518             existing.cancel();
519         }
520         if (animationType == FADE_IN) {
521             target.setPinnedStackSize(to, null);
522         }
523 
524         final BoundsAnimator animator = new BoundsAnimator(target, animationType, from, to,
525                 schedulePipModeChangedState, prevSchedulePipModeChangedState,
526                 moveFromFullscreen, moveToFullscreen, frozenTask);
527         mRunningAnimations.put(target, animator);
528         animator.setFloatValues(0f, 1f);
529         animator.setDuration(animationType == FADE_IN ? FADE_IN_DURATION
530                 : (animationDuration != -1 ? animationDuration : DEFAULT_TRANSITION_DURATION)
531                         * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
532         animator.setInterpolator(mFastOutSlowInInterpolator);
533         animator.start();
534         return animator;
535     }
536 
setAnimationType(@nimationType int animationType)537     public void setAnimationType(@AnimationType int animationType) {
538         mAnimationType = animationType;
539     }
540 
541     /** return the current animation type. */
getAnimationType()542     public @AnimationType int getAnimationType() {
543         @AnimationType int animationType = mAnimationType;
544         // Default to BOUNDS.
545         mAnimationType = BOUNDS;
546         return animationType;
547     }
548 
getHandler()549     public Handler getHandler() {
550         return mHandler;
551     }
552 
onAllWindowsDrawn()553     public void onAllWindowsDrawn() {
554         if (DEBUG) Slog.d(TAG, "onAllWindowsDrawn:");
555         mHandler.post(this::resume);
556     }
557 
isRunningFadeInAnimation(final BoundsAnimationTarget target)558     private boolean isRunningFadeInAnimation(final BoundsAnimationTarget target) {
559         final BoundsAnimator existing = mRunningAnimations.get(target);
560         return existing != null && existing.mAnimationType == FADE_IN && existing.isStarted();
561     }
562 
resume()563     private void resume() {
564         for (int i = 0; i < mRunningAnimations.size(); i++) {
565             final BoundsAnimator b = mRunningAnimations.valueAt(i);
566             b.resume();
567         }
568     }
569 
updateBooster()570     private void updateBooster() {
571         WindowManagerService.sThreadPriorityBooster.setBoundsAnimationRunning(
572                 !mRunningAnimations.isEmpty());
573     }
574 }
575