blob: 7b8057cafbb4126de745be41f1fd707015b17265 [file] [log] [blame]
Filip Gruszczynski84fa3352016-01-25 16:28:49 -08001/*
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
17package com.android.server.wm;
18
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080019import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
20import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
21import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
22
23import android.animation.Animator;
24import android.animation.ValueAnimator;
Winson Chungbaa7b722017-03-03 21:33:44 -080025import android.content.Context;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080026import android.graphics.Rect;
Wale Ogunwale2ba71292016-05-05 09:16:47 -070027import android.os.Handler;
Robert Carrf9aa2a92016-04-26 14:22:19 -070028import android.os.IBinder;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080029import android.os.Debug;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080030import android.util.ArrayMap;
31import android.util.Slog;
Winson Chungbaa7b722017-03-03 21:33:44 -080032import android.view.animation.AnimationUtils;
33import android.view.animation.Interpolator;
Robert Carrf9aa2a92016-04-26 14:22:19 -070034import android.view.WindowManagerInternal;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080035
36/**
37 * Enables animating bounds of objects.
38 *
39 * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
40 * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
41 * relaunching it would cause poorer experience), these class provides a way to directly animate
42 * the bounds of the resized object.
43 *
44 * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
Wale Ogunwale2ba71292016-05-05 09:16:47 -070045 *
46 * NOTE: All calls to methods in this class should be done on the UI thread
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080047 */
48public class BoundsAnimationController {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080049 private static final boolean DEBUG_LOCAL = false;
50 private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;
51 private static final String TAG = TAG_WITH_CLASS_NAME || DEBUG_LOCAL
52 ? "BoundsAnimationController" : TAG_WM;
Wale Ogunwalece144522016-02-05 22:51:01 -080053 private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080054
Winson Chungbaa7b722017-03-03 21:33:44 -080055 private static final int DEFAULT_TRANSITION_DURATION = 425;
56
Wale Ogunwalece144522016-02-05 22:51:01 -080057 // Only accessed on UI thread.
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080058 private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
59
Wale Ogunwale2ba71292016-05-05 09:16:47 -070060 private final class AppTransitionNotifier
61 extends WindowManagerInternal.AppTransitionListener implements Runnable {
Robert Carrf9aa2a92016-04-26 14:22:19 -070062
Wale Ogunwale2ba71292016-05-05 09:16:47 -070063 public void onAppTransitionCancelledLocked() {
Winson Chung87e5d552017-04-05 11:49:38 -070064 if (DEBUG) Slog.d(TAG, "onAppTransitionCancelledLocked:"
65 + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
Wale Ogunwale2ba71292016-05-05 09:16:47 -070066 animationFinished();
67 }
68 public void onAppTransitionFinishedLocked(IBinder token) {
Winson Chung87e5d552017-04-05 11:49:38 -070069 if (DEBUG) Slog.d(TAG, "onAppTransitionFinishedLocked:"
70 + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
Wale Ogunwale2ba71292016-05-05 09:16:47 -070071 animationFinished();
72 }
73 private void animationFinished() {
74 if (mFinishAnimationAfterTransition) {
75 mHandler.removeCallbacks(this);
Winson Chung87e5d552017-04-05 11:49:38 -070076 // This might end up calling into activity manager which will be bad since we have
77 // the window manager lock held at this point. Post a message to take care of the
78 // processing so we don't deadlock.
Wale Ogunwale2ba71292016-05-05 09:16:47 -070079 mHandler.post(this);
80 }
81 }
82
83 @Override
84 public void run() {
85 for (int i = 0; i < mRunningAnimations.size(); i++) {
86 final BoundsAnimator b = mRunningAnimations.valueAt(i);
87 b.onAnimationEnd(null);
88 }
89 }
90 }
91
92 private final Handler mHandler;
Robert Carrf9aa2a92016-04-26 14:22:19 -070093 private final AppTransition mAppTransition;
Wale Ogunwale2ba71292016-05-05 09:16:47 -070094 private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier();
Winson Chungbaa7b722017-03-03 21:33:44 -080095 private final Interpolator mFastOutSlowInInterpolator;
Robert Carrf9aa2a92016-04-26 14:22:19 -070096 private boolean mFinishAnimationAfterTransition = false;
97
Winson Chungbaa7b722017-03-03 21:33:44 -080098 BoundsAnimationController(Context context, AppTransition transition, Handler handler) {
Wale Ogunwale2ba71292016-05-05 09:16:47 -070099 mHandler = handler;
Robert Carrf9aa2a92016-04-26 14:22:19 -0700100 mAppTransition = transition;
101 mAppTransition.registerListenerLocked(mAppTransitionNotifier);
Winson Chungbaa7b722017-03-03 21:33:44 -0800102 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
103 com.android.internal.R.interpolator.fast_out_slow_in);
Robert Carrf9aa2a92016-04-26 14:22:19 -0700104 }
105
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800106 private final class BoundsAnimator extends ValueAnimator
107 implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
108 private final AnimateBoundsUser mTarget;
Winson Chung84a38342016-11-08 16:15:10 -0800109 private final Rect mFrom = new Rect();
110 private final Rect mTo = new Rect();
Robert Carr0d00c2e2016-02-29 17:45:02 -0800111 private final Rect mTmpRect = new Rect();
112 private final Rect mTmpTaskBounds = new Rect();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800113 private final boolean mMoveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -0800114 // True if this this animation was cancelled and will be replaced the another animation from
115 // the same {@link #AnimateBoundsUser} target.
Winson Chung5af42fc2017-03-24 17:11:33 -0700116 private boolean mSkipAnimationEnd;
117 // True if this animation replaced a previous animation of the same
Wale Ogunwalece144522016-02-05 22:51:01 -0800118 // {@link #AnimateBoundsUser} target.
Winson Chung5af42fc2017-03-24 17:11:33 -0700119 private final boolean mSkipAnimationStart;
120 // True if this animation is not replacing a previous animation, or if the previous
121 // animation is animating to a different fullscreen state than the current animation.
122 // We use this to ensure that we always provide a consistent set/order of callbacks when we
123 // transition to/from PiP.
124 private final boolean mAnimatingToNewFullscreenState;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800125
Robert Carr0d00c2e2016-02-29 17:45:02 -0800126 // Depending on whether we are animating from
127 // a smaller to a larger size
128 private final int mFrozenTaskWidth;
129 private final int mFrozenTaskHeight;
130
Winson Chung08f81892017-03-02 15:40:51 -0800131 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen,
Winson Chung5af42fc2017-03-24 17:11:33 -0700132 boolean replacingExistingAnimation, boolean animatingToNewFullscreenState) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800133 super();
134 mTarget = target;
Winson Chung84a38342016-11-08 16:15:10 -0800135 mFrom.set(from);
136 mTo.set(to);
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800137 mMoveToFullScreen = moveToFullScreen;
Winson Chung5af42fc2017-03-24 17:11:33 -0700138 mSkipAnimationStart = replacingExistingAnimation;
139 mAnimatingToNewFullscreenState = animatingToNewFullscreenState;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800140 addUpdateListener(this);
141 addListener(this);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800142
143 // If we are animating from smaller to larger, we want to change the task bounds
144 // to their final size immediately so we can use scaling to make the window
145 // larger. Likewise if we are going from bigger to smaller, we want to wait until
146 // the end so we don't have to upscale from the smaller finished size.
147 if (animatingToLargerSize()) {
148 mFrozenTaskWidth = mTo.width();
149 mFrozenTaskHeight = mTo.height();
150 } else {
151 mFrozenTaskWidth = mFrom.width();
152 mFrozenTaskHeight = mFrom.height();
153 }
154 }
155
Winson Chung5af42fc2017-03-24 17:11:33 -0700156 @Override
157 public void onAnimationStart(Animator animation) {
158 if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
159 + " mSkipAnimationStart=" + mSkipAnimationStart);
160 mFinishAnimationAfterTransition = false;
161 mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
162 mFrom.top + mFrozenTaskHeight);
163
164 // Ensure that we have prepared the target for animation before
165 // we trigger any size changes, so it can swap surfaces
166 // in to appropriate modes, or do as it wishes otherwise.
167 if (!mSkipAnimationStart) {
168 mTarget.onAnimationStart(mMoveToFullScreen);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800169 }
Winson Chung5af42fc2017-03-24 17:11:33 -0700170
171 // If we are animating to a new fullscreen state (either to/from fullscreen), then
172 // notify the target of the change with the new frozen task bounds
173 if (mAnimatingToNewFullscreenState) {
174 mTarget.updatePictureInPictureMode(mMoveToFullScreen ? null : mTo);
175 }
176
177 // Immediately update the task bounds if they have to become larger, but preserve
178 // the starting position so we don't jump at the beginning of the animation.
179 if (animatingToLargerSize()) {
180 mTarget.setPinnedStackSize(mFrom, mTmpRect);
181 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800182 }
183
184 @Override
185 public void onAnimationUpdate(ValueAnimator animation) {
186 final float value = (Float) animation.getAnimatedValue();
187 final float remains = 1 - value;
Wale Ogunwalece144522016-02-05 22:51:01 -0800188 mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
189 mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
190 mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
191 mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800192 if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
193 + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
194 + " remains=" + remains);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800195
Robert Carrc7294602016-05-13 11:32:05 -0700196 mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
197 mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800198
Robert Carrc7294602016-05-13 11:32:05 -0700199 if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800200 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
201 // any further animation.
Winson Chung87e5d552017-04-05 11:49:38 -0700202 if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800203 animation.cancel();
204 }
205 }
206
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800207 @Override
208 public void onAnimationEnd(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800209 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
Winson Chung5af42fc2017-03-24 17:11:33 -0700210 + " mMoveToFullScreen=" + mMoveToFullScreen
Winson Chung87e5d552017-04-05 11:49:38 -0700211 + " mSkipAnimationEnd=" + mSkipAnimationEnd
212 + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
213 + " mAppTransitionIsRunning=" + mAppTransition.isRunning());
Jaewan Kimb0033642016-04-22 18:41:37 +0900214
Robert Carrf9aa2a92016-04-26 14:22:19 -0700215 // There could be another animation running. For example in the
216 // move to fullscreen case, recents will also be closing while the
217 // previous task will be taking its place in the fullscreen stack.
218 // we have to ensure this is completed before we finish the animation
219 // and take our place in the fullscreen stack.
220 if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
221 mFinishAnimationAfterTransition = true;
222 return;
223 }
224
Robert Carrc7294602016-05-13 11:32:05 -0700225 finishAnimation();
226
227 mTarget.setPinnedStackSize(mTo, null);
Winson Chung5af42fc2017-03-24 17:11:33 -0700228 if (mMoveToFullScreen && !mSkipAnimationEnd) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800229 mTarget.moveToFullscreen();
230 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800231 }
232
233 @Override
234 public void onAnimationCancel(Animator animation) {
235 finishAnimation();
236 }
237
Wale Ogunwalece144522016-02-05 22:51:01 -0800238 @Override
239 public void cancel() {
Winson Chung5af42fc2017-03-24 17:11:33 -0700240 mSkipAnimationEnd = true;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800241 if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
Wale Ogunwalece144522016-02-05 22:51:01 -0800242 super.cancel();
243 }
244
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800245 /** Returns true if the animation target is the same as the input bounds. */
Winson Chung5af42fc2017-03-24 17:11:33 -0700246 boolean isAnimatingTo(Rect bounds) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800247 return mTo.equals(bounds);
248 }
249
Winson Chung5af42fc2017-03-24 17:11:33 -0700250 private boolean animatingToLargerSize() {
251 if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
252 return false;
253 }
254 return true;
255 }
256
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800257 private void finishAnimation() {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800258 if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
259 + " callers" + Debug.getCallers(2));
Winson Chung5af42fc2017-03-24 17:11:33 -0700260 if (!mSkipAnimationEnd) {
Wale Ogunwalece144522016-02-05 22:51:01 -0800261 mTarget.onAnimationEnd();
262 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800263 removeListener(this);
264 removeUpdateListener(this);
265 mRunningAnimations.remove(mTarget);
266 }
267
268 @Override
269 public void onAnimationRepeat(Animator animation) {
Winson Chung5af42fc2017-03-24 17:11:33 -0700270 // Do nothing
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800271 }
272 }
273
274 public interface AnimateBoundsUser {
275 /**
276 * Asks the target to directly (without any intermediate steps, like scheduling animation)
277 * resize its bounds.
278 *
279 * @return Whether the target still wants to be animated and successfully finished the
280 * operation. If it returns false, the animation will immediately be cancelled. The target
281 * should return false when something abnormal happened, e.g. it was completely removed
282 * from the hierarchy and is not valid anymore.
283 */
284 boolean setSize(Rect bounds);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800285 /**
286 * Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
Winson Chung5af42fc2017-03-24 17:11:33 -0700287 * to allow for more flexibility during resizing. Only works for the pinned stack at the
288 * moment.
Robert Carr0d00c2e2016-02-29 17:45:02 -0800289 */
290 boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800291
Winson Chung5af42fc2017-03-24 17:11:33 -0700292 /**
293 * Callback for the target to inform it that the animation has started, so it can do some
294 * necessary preparation.
295 */
Robert Carr7e4c90e2017-02-15 19:52:38 -0800296 void onAnimationStart(boolean toFullscreen);
Wale Ogunwalece144522016-02-05 22:51:01 -0800297
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800298 /**
Winson Chung5af42fc2017-03-24 17:11:33 -0700299 * Callback for the target to inform it that the animation is going to a new fullscreen
300 * state and should update the picture-in-picture mode accordingly.
301 *
302 * @param targetStackBounds the target stack bounds we are animating to, can be null if
303 * we are animating to fullscreen
304 */
305 void updatePictureInPictureMode(Rect targetStackBounds);
306
307 /**
Wale Ogunwalece144522016-02-05 22:51:01 -0800308 * Callback for the target to inform it that the animation has ended, so it can do some
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800309 * necessary cleanup.
310 */
Wale Ogunwalece144522016-02-05 22:51:01 -0800311 void onAnimationEnd();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800312
313 void moveToFullscreen();
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800314 }
315
Winson Chung55893332017-02-17 17:13:10 -0800316 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration,
317 boolean moveToFullscreen) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800318 final BoundsAnimator existing = mRunningAnimations.get(target);
Wale Ogunwalece144522016-02-05 22:51:01 -0800319 final boolean replacing = existing != null;
Winson Chung5af42fc2017-03-24 17:11:33 -0700320 final boolean animatingToNewFullscreenState = (existing == null) ||
321 (existing.mMoveToFullScreen != moveToFullscreen);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800322
323 if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
Winson Chung5af42fc2017-03-24 17:11:33 -0700324 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing
325 + " animatingToNewFullscreenState=" + animatingToNewFullscreenState);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800326
Wale Ogunwalece144522016-02-05 22:51:01 -0800327 if (replacing) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800328 if (existing.isAnimatingTo(to)) {
329 // Just les the current animation complete if it has the same destination as the
330 // one we are trying to start.
331 if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
332 + " ignoring...");
333 return;
334 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800335 existing.cancel();
336 }
Winson Chung5af42fc2017-03-24 17:11:33 -0700337 final BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen,
338 replacing, animatingToNewFullscreenState);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800339 mRunningAnimations.put(target, animator);
340 animator.setFloatValues(0f, 1f);
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700341 animator.setDuration((animationDuration != -1 ? animationDuration
Winson Chungbaa7b722017-03-03 21:33:44 -0800342 : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
343 animator.setInterpolator(mFastOutSlowInInterpolator);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800344 animator.start();
345 }
346}