• 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.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
20 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
21 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
23 
24 import android.animation.Animator;
25 import android.animation.ValueAnimator;
26 import android.graphics.Rect;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Debug;
30 import android.util.ArrayMap;
31 import android.util.Slog;
32 import android.view.animation.LinearInterpolator;
33 import android.view.WindowManagerInternal;
34 
35 /**
36  * Enables animating bounds of objects.
37  *
38  * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
39  * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
40  * relaunching it would cause poorer experience), these class provides a way to directly animate
41  * the bounds of the resized object.
42  *
43  * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
44  *
45  * NOTE: All calls to methods in this class should be done on the UI thread
46  */
47 public class BoundsAnimationController {
48     private static final boolean DEBUG_LOCAL = false;
49     private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;
50     private static final String TAG = TAG_WITH_CLASS_NAME || DEBUG_LOCAL
51             ? "BoundsAnimationController" : TAG_WM;
52     private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
53 
54     // Only accessed on UI thread.
55     private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
56 
57     private final class AppTransitionNotifier
58             extends WindowManagerInternal.AppTransitionListener implements Runnable {
59 
onAppTransitionCancelledLocked()60         public void onAppTransitionCancelledLocked() {
61             animationFinished();
62         }
onAppTransitionFinishedLocked(IBinder token)63         public void onAppTransitionFinishedLocked(IBinder token) {
64             animationFinished();
65         }
animationFinished()66         private void animationFinished() {
67             if (mFinishAnimationAfterTransition) {
68                 mHandler.removeCallbacks(this);
69                 // This might end up calling into activity manager which will be bad since we have the
70                 // window manager lock held at this point. Post a message to take care of the processing
71                 // so we don't deadlock.
72                 mHandler.post(this);
73             }
74         }
75 
76         @Override
run()77         public void run() {
78             for (int i = 0; i < mRunningAnimations.size(); i++) {
79                 final BoundsAnimator b = mRunningAnimations.valueAt(i);
80                 b.onAnimationEnd(null);
81             }
82         }
83     }
84 
85     private final Handler mHandler;
86     private final AppTransition mAppTransition;
87     private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier();
88     private boolean mFinishAnimationAfterTransition = false;
89 
BoundsAnimationController(AppTransition transition, Handler handler)90     BoundsAnimationController(AppTransition transition, Handler handler) {
91         mHandler = handler;
92         mAppTransition = transition;
93         mAppTransition.registerListenerLocked(mAppTransitionNotifier);
94     }
95 
96     private final class BoundsAnimator extends ValueAnimator
97             implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
98         private final AnimateBoundsUser mTarget;
99         private final Rect mFrom;
100         private final Rect mTo;
101         private final Rect mTmpRect = new Rect();
102         private final Rect mTmpTaskBounds = new Rect();
103         private final boolean mMoveToFullScreen;
104         // True if this this animation was cancelled and will be replaced the another animation from
105         // the same {@link #AnimateBoundsUser} target.
106         private boolean mWillReplace;
107         // True to true if this animation replaced a previous animation of the same
108         // {@link #AnimateBoundsUser} target.
109         private final boolean mReplacement;
110 
111         // Depending on whether we are animating from
112         // a smaller to a larger size
113         private final int mFrozenTaskWidth;
114         private final int mFrozenTaskHeight;
115 
BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen, boolean replacement)116         BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
117                 boolean moveToFullScreen, boolean replacement) {
118             super();
119             mTarget = target;
120             mFrom = from;
121             mTo = to;
122             mMoveToFullScreen = moveToFullScreen;
123             mReplacement = replacement;
124             addUpdateListener(this);
125             addListener(this);
126 
127             // If we are animating from smaller to larger, we want to change the task bounds
128             // to their final size immediately so we can use scaling to make the window
129             // larger. Likewise if we are going from bigger to smaller, we want to wait until
130             // the end so we don't have to upscale from the smaller finished size.
131             if (animatingToLargerSize()) {
132                 mFrozenTaskWidth = mTo.width();
133                 mFrozenTaskHeight = mTo.height();
134             } else {
135                 mFrozenTaskWidth = mFrom.width();
136                 mFrozenTaskHeight = mFrom.height();
137             }
138         }
139 
animatingToLargerSize()140         boolean animatingToLargerSize() {
141             if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
142                 return false;
143             }
144             return true;
145         }
146 
147         @Override
onAnimationUpdate(ValueAnimator animation)148         public void onAnimationUpdate(ValueAnimator animation) {
149             final float value = (Float) animation.getAnimatedValue();
150             final float remains = 1 - value;
151             mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
152             mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
153             mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
154             mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
155             if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
156                     + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
157                     + " remains=" + remains);
158 
159             mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
160                     mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
161 
162             if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
163                 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
164                 // any further animation.
165                 animation.cancel();
166             }
167         }
168 
169 
170         @Override
onAnimationStart(Animator animation)171         public void onAnimationStart(Animator animation) {
172             if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
173                     + " mReplacement=" + mReplacement);
174             mFinishAnimationAfterTransition = false;
175             // Ensure that we have prepared the target for animation before
176             // we trigger any size changes, so it can swap surfaces
177             // in to appropriate modes, or do as it wishes otherwise.
178             if (!mReplacement) {
179                 mTarget.onAnimationStart();
180             }
181 
182             // Immediately update the task bounds if they have to become larger, but preserve
183             // the starting position so we don't jump at the beginning of the animation.
184             if (animatingToLargerSize()) {
185                 mTmpRect.set(mFrom.left, mFrom.top,
186                         mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight);
187                 mTarget.setPinnedStackSize(mFrom, mTmpRect);
188             }
189         }
190 
191         @Override
onAnimationEnd(Animator animation)192         public void onAnimationEnd(Animator animation) {
193             if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
194                     + " mMoveToFullScreen=" + mMoveToFullScreen + " mWillReplace=" + mWillReplace);
195 
196             // There could be another animation running. For example in the
197             // move to fullscreen case, recents will also be closing while the
198             // previous task will be taking its place in the fullscreen stack.
199             // we have to ensure this is completed before we finish the animation
200             // and take our place in the fullscreen stack.
201             if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
202                 mFinishAnimationAfterTransition = true;
203                 return;
204             }
205 
206             finishAnimation();
207 
208             mTarget.setPinnedStackSize(mTo, null);
209             if (mMoveToFullScreen && !mWillReplace) {
210                 mTarget.moveToFullscreen();
211             }
212         }
213 
214         @Override
onAnimationCancel(Animator animation)215         public void onAnimationCancel(Animator animation) {
216             finishAnimation();
217         }
218 
219         @Override
cancel()220         public void cancel() {
221             mWillReplace = true;
222             if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
223             super.cancel();
224         }
225 
226         /** Returns true if the animation target is the same as the input bounds. */
isAnimatingTo(Rect bounds)227         public boolean isAnimatingTo(Rect bounds) {
228             return mTo.equals(bounds);
229         }
230 
finishAnimation()231         private void finishAnimation() {
232             if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
233                     + " callers" + Debug.getCallers(2));
234             if (!mWillReplace) {
235                 mTarget.onAnimationEnd();
236             }
237             removeListener(this);
238             removeUpdateListener(this);
239             mRunningAnimations.remove(mTarget);
240         }
241 
242         @Override
onAnimationRepeat(Animator animation)243         public void onAnimationRepeat(Animator animation) {
244 
245         }
246     }
247 
248     public interface AnimateBoundsUser {
249         /**
250          * Asks the target to directly (without any intermediate steps, like scheduling animation)
251          * resize its bounds.
252          *
253          * @return Whether the target still wants to be animated and successfully finished the
254          * operation. If it returns false, the animation will immediately be cancelled. The target
255          * should return false when something abnormal happened, e.g. it was completely removed
256          * from the hierarchy and is not valid anymore.
257          */
setSize(Rect bounds)258         boolean setSize(Rect bounds);
259         /**
260          * Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
261          * to allow for more flexibility during resizing. Only
262          * works for the pinned stack at the moment.
263          */
setPinnedStackSize(Rect bounds, Rect taskBounds)264         boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
265 
onAnimationStart()266         void onAnimationStart();
267 
268         /**
269          * Callback for the target to inform it that the animation has ended, so it can do some
270          * necessary cleanup.
271          */
onAnimationEnd()272         void onAnimationEnd();
273 
moveToFullscreen()274         void moveToFullscreen();
275 
getFullScreenBounds(Rect bounds)276         void getFullScreenBounds(Rect bounds);
277     }
278 
animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration)279     void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration) {
280         boolean moveToFullscreen = false;
281         if (to == null) {
282             to = new Rect();
283             target.getFullScreenBounds(to);
284             moveToFullscreen = true;
285         }
286 
287         final BoundsAnimator existing = mRunningAnimations.get(target);
288         final boolean replacing = existing != null;
289 
290         if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
291                 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing);
292 
293         if (replacing) {
294             if (existing.isAnimatingTo(to)) {
295                 // Just les the current animation complete if it has the same destination as the
296                 // one we are trying to start.
297                 if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
298                         + " ignoring...");
299                 return;
300             }
301             existing.cancel();
302         }
303         final BoundsAnimator animator =
304                 new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
305         mRunningAnimations.put(target, animator);
306         animator.setFloatValues(0f, 1f);
307         animator.setDuration((animationDuration != -1 ? animationDuration
308                 : DEFAULT_APP_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
309         animator.setInterpolator(new LinearInterpolator());
310         animator.start();
311     }
312 }
313