blob: 51c40456c66a54b1fe5e92b59991b243a89473e8 [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
19import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
20import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
21import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
22import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
23
24import android.animation.Animator;
25import android.animation.ValueAnimator;
26import 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;
32import android.view.animation.LinearInterpolator;
Robert Carrf9aa2a92016-04-26 14:22:19 -070033import android.view.WindowManagerInternal;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080034
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.
Wale Ogunwale2ba71292016-05-05 09:16:47 -070044 *
45 * NOTE: All calls to methods in this class should be done on the UI thread
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080046 */
47public class BoundsAnimationController {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080048 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;
Wale Ogunwalece144522016-02-05 22:51:01 -080052 private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080053
Wale Ogunwalece144522016-02-05 22:51:01 -080054 // Only accessed on UI thread.
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080055 private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
56
Wale Ogunwale2ba71292016-05-05 09:16:47 -070057 private final class AppTransitionNotifier
58 extends WindowManagerInternal.AppTransitionListener implements Runnable {
Robert Carrf9aa2a92016-04-26 14:22:19 -070059
Wale Ogunwale2ba71292016-05-05 09:16:47 -070060 public void onAppTransitionCancelledLocked() {
61 animationFinished();
62 }
63 public void onAppTransitionFinishedLocked(IBinder token) {
64 animationFinished();
65 }
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
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;
Robert Carrf9aa2a92016-04-26 14:22:19 -070086 private final AppTransition mAppTransition;
Wale Ogunwale2ba71292016-05-05 09:16:47 -070087 private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier();
Robert Carrf9aa2a92016-04-26 14:22:19 -070088 private boolean mFinishAnimationAfterTransition = false;
89
Wale Ogunwale2ba71292016-05-05 09:16:47 -070090 BoundsAnimationController(AppTransition transition, Handler handler) {
91 mHandler = handler;
Robert Carrf9aa2a92016-04-26 14:22:19 -070092 mAppTransition = transition;
93 mAppTransition.registerListenerLocked(mAppTransitionNotifier);
94 }
95
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080096 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;
Robert Carr0d00c2e2016-02-29 17:45:02 -0800101 private final Rect mTmpRect = new Rect();
102 private final Rect mTmpTaskBounds = new Rect();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800103 private final boolean mMoveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -0800104 // 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;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800110
Robert Carr0d00c2e2016-02-29 17:45:02 -0800111 // 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
Wale Ogunwalece144522016-02-05 22:51:01 -0800116 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
117 boolean moveToFullScreen, boolean replacement) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800118 super();
119 mTarget = target;
120 mFrom = from;
121 mTo = to;
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800122 mMoveToFullScreen = moveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -0800123 mReplacement = replacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800124 addUpdateListener(this);
125 addListener(this);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800126
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
140 boolean animatingToLargerSize() {
141 if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
142 return false;
143 }
144 return true;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800145 }
146
147 @Override
148 public void onAnimationUpdate(ValueAnimator animation) {
149 final float value = (Float) animation.getAnimatedValue();
150 final float remains = 1 - value;
Wale Ogunwalece144522016-02-05 22:51:01 -0800151 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);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800155 if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
156 + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
157 + " remains=" + remains);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800158
159 if (remains != 0) {
160 mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
161 mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
162 }
163
164 if (!mTarget.setPinnedStackSize(mTmpRect, remains != 0 ? mTmpTaskBounds : null)) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800165 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
166 // any further animation.
167 animation.cancel();
168 }
169 }
170
171
172 @Override
173 public void onAnimationStart(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800174 if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
175 + " mReplacement=" + mReplacement);
Robert Carrf9aa2a92016-04-26 14:22:19 -0700176 mFinishAnimationAfterTransition = false;
Robert Carr69016e32016-04-18 11:52:21 -0700177 // Ensure that we have prepared the target for animation before
178 // we trigger any size changes, so it can swap surfaces
179 // in to appropriate modes, or do as it wishes otherwise.
Wale Ogunwalece144522016-02-05 22:51:01 -0800180 if (!mReplacement) {
181 mTarget.onAnimationStart();
182 }
Robert Carr1ca6a332016-04-11 18:00:43 -0700183
Robert Carr69016e32016-04-18 11:52:21 -0700184 // Immediately update the task bounds if they have to become larger, but preserve
185 // the starting position so we don't jump at the beginning of the animation.
Robert Carr1ca6a332016-04-11 18:00:43 -0700186 if (animatingToLargerSize()) {
Robert Carr69016e32016-04-18 11:52:21 -0700187 mTmpRect.set(mFrom.left, mFrom.top,
188 mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight);
189 mTarget.setPinnedStackSize(mFrom, mTmpRect);
Robert Carr1ca6a332016-04-11 18:00:43 -0700190 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800191 }
192
193 @Override
194 public void onAnimationEnd(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800195 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
196 + " mMoveToFullScreen=" + mMoveToFullScreen + " mWillReplace=" + mWillReplace);
Jaewan Kimb0033642016-04-22 18:41:37 +0900197
Robert Carrf9aa2a92016-04-26 14:22:19 -0700198 // There could be another animation running. For example in the
199 // move to fullscreen case, recents will also be closing while the
200 // previous task will be taking its place in the fullscreen stack.
201 // we have to ensure this is completed before we finish the animation
202 // and take our place in the fullscreen stack.
203 if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
204 mFinishAnimationAfterTransition = true;
205 return;
206 }
207
Wale Ogunwalece144522016-02-05 22:51:01 -0800208 if (mMoveToFullScreen && !mWillReplace) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800209 mTarget.moveToFullscreen();
210 }
Robert Carrf9aa2a92016-04-26 14:22:19 -0700211
212 finishAnimation();
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800213 }
214
215 @Override
216 public void onAnimationCancel(Animator animation) {
217 finishAnimation();
218 }
219
Wale Ogunwalece144522016-02-05 22:51:01 -0800220 @Override
221 public void cancel() {
222 mWillReplace = true;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800223 if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
Wale Ogunwalece144522016-02-05 22:51:01 -0800224 super.cancel();
225 }
226
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800227 /** Returns true if the animation target is the same as the input bounds. */
228 public boolean isAnimatingTo(Rect bounds) {
229 return mTo.equals(bounds);
230 }
231
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800232 private void finishAnimation() {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800233 if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
234 + " callers" + Debug.getCallers(2));
Wale Ogunwalece144522016-02-05 22:51:01 -0800235 if (!mWillReplace) {
236 mTarget.onAnimationEnd();
237 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800238 removeListener(this);
239 removeUpdateListener(this);
240 mRunningAnimations.remove(mTarget);
241 }
242
243 @Override
244 public void onAnimationRepeat(Animator animation) {
245
246 }
247 }
248
249 public interface AnimateBoundsUser {
250 /**
251 * Asks the target to directly (without any intermediate steps, like scheduling animation)
252 * resize its bounds.
253 *
254 * @return Whether the target still wants to be animated and successfully finished the
255 * operation. If it returns false, the animation will immediately be cancelled. The target
256 * should return false when something abnormal happened, e.g. it was completely removed
257 * from the hierarchy and is not valid anymore.
258 */
259 boolean setSize(Rect bounds);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800260 /**
261 * Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
262 * to allow for more flexibility during resizing. Only
263 * works for the pinned stack at the moment.
264 */
265 boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800266
Wale Ogunwalece144522016-02-05 22:51:01 -0800267 void onAnimationStart();
268
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800269 /**
Wale Ogunwalece144522016-02-05 22:51:01 -0800270 * Callback for the target to inform it that the animation has ended, so it can do some
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800271 * necessary cleanup.
272 */
Wale Ogunwalece144522016-02-05 22:51:01 -0800273 void onAnimationEnd();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800274
275 void moveToFullscreen();
276
277 void getFullScreenBounds(Rect bounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800278 }
279
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700280 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800281 boolean moveToFullscreen = false;
282 if (to == null) {
283 to = new Rect();
284 target.getFullScreenBounds(to);
285 moveToFullscreen = true;
286 }
287
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800288 final BoundsAnimator existing = mRunningAnimations.get(target);
Wale Ogunwalece144522016-02-05 22:51:01 -0800289 final boolean replacing = existing != null;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800290
291 if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
292 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing);
293
Wale Ogunwalece144522016-02-05 22:51:01 -0800294 if (replacing) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800295 if (existing.isAnimatingTo(to)) {
296 // Just les the current animation complete if it has the same destination as the
297 // one we are trying to start.
298 if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
299 + " ignoring...");
300 return;
301 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800302 existing.cancel();
303 }
Wale Ogunwalece144522016-02-05 22:51:01 -0800304 final BoundsAnimator animator =
305 new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800306 mRunningAnimations.put(target, animator);
307 animator.setFloatValues(0f, 1f);
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700308 animator.setDuration((animationDuration != -1 ? animationDuration
309 : DEFAULT_APP_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800310 animator.setInterpolator(new LinearInterpolator());
311 animator.start();
312 }
313}