blob: 220a6de1d88880745a107f506602d3069e72db28 [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;
Robert Carrf9aa2a92016-04-26 14:22:19 -070027import android.os.IBinder;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080028import android.os.Debug;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080029import android.util.ArrayMap;
30import android.util.Slog;
31import android.view.animation.LinearInterpolator;
Robert Carrf9aa2a92016-04-26 14:22:19 -070032import android.view.WindowManagerInternal;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080033
34/**
35 * Enables animating bounds of objects.
36 *
37 * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
38 * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
39 * relaunching it would cause poorer experience), these class provides a way to directly animate
40 * the bounds of the resized object.
41 *
42 * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
43 */
44public class BoundsAnimationController {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080045 private static final boolean DEBUG_LOCAL = false;
46 private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;
47 private static final String TAG = TAG_WITH_CLASS_NAME || DEBUG_LOCAL
48 ? "BoundsAnimationController" : TAG_WM;
Wale Ogunwalece144522016-02-05 22:51:01 -080049 private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080050
Wale Ogunwalece144522016-02-05 22:51:01 -080051 // Only accessed on UI thread.
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080052 private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
53
Robert Carrf9aa2a92016-04-26 14:22:19 -070054 private final WindowManagerInternal.AppTransitionListener mAppTransitionNotifier
55 = new WindowManagerInternal.AppTransitionListener() {
56 public void onAppTransitionCancelledLocked() {
57 animationFinished();
58 }
59 public void onAppTransitionFinishedLocked(IBinder token) {
60 animationFinished();
61 }
62 private void animationFinished() {
63 if (mFinishAnimationAfterTransition) {
64 for (int i = 0; i < mRunningAnimations.size(); i++) {
65 BoundsAnimator b = mRunningAnimations.valueAt(i);
66 b.onAnimationEnd(null);
67 }
68 }
69 }
70 };
71
72 private final AppTransition mAppTransition;
73 private boolean mFinishAnimationAfterTransition = false;
74
75 BoundsAnimationController(AppTransition transition) {
76 mAppTransition = transition;
77 mAppTransition.registerListenerLocked(mAppTransitionNotifier);
78 }
79
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080080 private final class BoundsAnimator extends ValueAnimator
81 implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
82 private final AnimateBoundsUser mTarget;
83 private final Rect mFrom;
84 private final Rect mTo;
Robert Carr0d00c2e2016-02-29 17:45:02 -080085 private final Rect mTmpRect = new Rect();
86 private final Rect mTmpTaskBounds = new Rect();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -080087 private final boolean mMoveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -080088 // True if this this animation was cancelled and will be replaced the another animation from
89 // the same {@link #AnimateBoundsUser} target.
90 private boolean mWillReplace;
91 // True to true if this animation replaced a previous animation of the same
92 // {@link #AnimateBoundsUser} target.
93 private final boolean mReplacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080094
Robert Carr0d00c2e2016-02-29 17:45:02 -080095 // Depending on whether we are animating from
96 // a smaller to a larger size
97 private final int mFrozenTaskWidth;
98 private final int mFrozenTaskHeight;
99
Wale Ogunwalece144522016-02-05 22:51:01 -0800100 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
101 boolean moveToFullScreen, boolean replacement) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800102 super();
103 mTarget = target;
104 mFrom = from;
105 mTo = to;
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800106 mMoveToFullScreen = moveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -0800107 mReplacement = replacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800108 addUpdateListener(this);
109 addListener(this);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800110
111 // If we are animating from smaller to larger, we want to change the task bounds
112 // to their final size immediately so we can use scaling to make the window
113 // larger. Likewise if we are going from bigger to smaller, we want to wait until
114 // the end so we don't have to upscale from the smaller finished size.
115 if (animatingToLargerSize()) {
116 mFrozenTaskWidth = mTo.width();
117 mFrozenTaskHeight = mTo.height();
118 } else {
119 mFrozenTaskWidth = mFrom.width();
120 mFrozenTaskHeight = mFrom.height();
121 }
122 }
123
124 boolean animatingToLargerSize() {
125 if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
126 return false;
127 }
128 return true;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800129 }
130
131 @Override
132 public void onAnimationUpdate(ValueAnimator animation) {
133 final float value = (Float) animation.getAnimatedValue();
134 final float remains = 1 - value;
Wale Ogunwalece144522016-02-05 22:51:01 -0800135 mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
136 mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
137 mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
138 mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800139 if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
140 + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
141 + " remains=" + remains);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800142
143 if (remains != 0) {
144 mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
145 mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
146 }
147
148 if (!mTarget.setPinnedStackSize(mTmpRect, remains != 0 ? mTmpTaskBounds : null)) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800149 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
150 // any further animation.
151 animation.cancel();
152 }
153 }
154
155
156 @Override
157 public void onAnimationStart(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800158 if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
159 + " mReplacement=" + mReplacement);
Robert Carrf9aa2a92016-04-26 14:22:19 -0700160 mFinishAnimationAfterTransition = false;
Robert Carr69016e32016-04-18 11:52:21 -0700161 // Ensure that we have prepared the target for animation before
162 // we trigger any size changes, so it can swap surfaces
163 // in to appropriate modes, or do as it wishes otherwise.
Wale Ogunwalece144522016-02-05 22:51:01 -0800164 if (!mReplacement) {
165 mTarget.onAnimationStart();
166 }
Robert Carr1ca6a332016-04-11 18:00:43 -0700167
Robert Carr69016e32016-04-18 11:52:21 -0700168 // Immediately update the task bounds if they have to become larger, but preserve
169 // the starting position so we don't jump at the beginning of the animation.
Robert Carr1ca6a332016-04-11 18:00:43 -0700170 if (animatingToLargerSize()) {
Robert Carr69016e32016-04-18 11:52:21 -0700171 mTmpRect.set(mFrom.left, mFrom.top,
172 mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight);
173 mTarget.setPinnedStackSize(mFrom, mTmpRect);
Robert Carr1ca6a332016-04-11 18:00:43 -0700174 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800175 }
176
177 @Override
178 public void onAnimationEnd(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800179 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
180 + " mMoveToFullScreen=" + mMoveToFullScreen + " mWillReplace=" + mWillReplace);
Jaewan Kimb0033642016-04-22 18:41:37 +0900181
Robert Carrf9aa2a92016-04-26 14:22:19 -0700182 // There could be another animation running. For example in the
183 // move to fullscreen case, recents will also be closing while the
184 // previous task will be taking its place in the fullscreen stack.
185 // we have to ensure this is completed before we finish the animation
186 // and take our place in the fullscreen stack.
187 if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
188 mFinishAnimationAfterTransition = true;
189 return;
190 }
191
Wale Ogunwalece144522016-02-05 22:51:01 -0800192 if (mMoveToFullScreen && !mWillReplace) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800193 mTarget.moveToFullscreen();
194 }
Robert Carrf9aa2a92016-04-26 14:22:19 -0700195
196 finishAnimation();
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800197 }
198
199 @Override
200 public void onAnimationCancel(Animator animation) {
201 finishAnimation();
202 }
203
Wale Ogunwalece144522016-02-05 22:51:01 -0800204 @Override
205 public void cancel() {
206 mWillReplace = true;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800207 if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
Wale Ogunwalece144522016-02-05 22:51:01 -0800208 super.cancel();
209 }
210
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800211 /** Returns true if the animation target is the same as the input bounds. */
212 public boolean isAnimatingTo(Rect bounds) {
213 return mTo.equals(bounds);
214 }
215
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800216 private void finishAnimation() {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800217 if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
218 + " callers" + Debug.getCallers(2));
Wale Ogunwalece144522016-02-05 22:51:01 -0800219 if (!mWillReplace) {
220 mTarget.onAnimationEnd();
221 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800222 removeListener(this);
223 removeUpdateListener(this);
224 mRunningAnimations.remove(mTarget);
225 }
226
227 @Override
228 public void onAnimationRepeat(Animator animation) {
229
230 }
231 }
232
233 public interface AnimateBoundsUser {
234 /**
235 * Asks the target to directly (without any intermediate steps, like scheduling animation)
236 * resize its bounds.
237 *
238 * @return Whether the target still wants to be animated and successfully finished the
239 * operation. If it returns false, the animation will immediately be cancelled. The target
240 * should return false when something abnormal happened, e.g. it was completely removed
241 * from the hierarchy and is not valid anymore.
242 */
243 boolean setSize(Rect bounds);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800244 /**
245 * Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
246 * to allow for more flexibility during resizing. Only
247 * works for the pinned stack at the moment.
248 */
249 boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800250
Wale Ogunwalece144522016-02-05 22:51:01 -0800251 void onAnimationStart();
252
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800253 /**
Wale Ogunwalece144522016-02-05 22:51:01 -0800254 * Callback for the target to inform it that the animation has ended, so it can do some
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800255 * necessary cleanup.
256 */
Wale Ogunwalece144522016-02-05 22:51:01 -0800257 void onAnimationEnd();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800258
259 void moveToFullscreen();
260
261 void getFullScreenBounds(Rect bounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800262 }
263
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700264 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800265 boolean moveToFullscreen = false;
266 if (to == null) {
267 to = new Rect();
268 target.getFullScreenBounds(to);
269 moveToFullscreen = true;
270 }
271
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800272 final BoundsAnimator existing = mRunningAnimations.get(target);
Wale Ogunwalece144522016-02-05 22:51:01 -0800273 final boolean replacing = existing != null;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800274
275 if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
276 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing);
277
Wale Ogunwalece144522016-02-05 22:51:01 -0800278 if (replacing) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800279 if (existing.isAnimatingTo(to)) {
280 // Just les the current animation complete if it has the same destination as the
281 // one we are trying to start.
282 if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
283 + " ignoring...");
284 return;
285 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800286 existing.cancel();
287 }
Wale Ogunwalece144522016-02-05 22:51:01 -0800288 final BoundsAnimator animator =
289 new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800290 mRunningAnimations.put(target, animator);
291 animator.setFloatValues(0f, 1f);
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700292 animator.setDuration((animationDuration != -1 ? animationDuration
293 : DEFAULT_APP_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800294 animator.setInterpolator(new LinearInterpolator());
295 animator.start();
296 }
297}