blob: cd0e6ccb2eb451e6e65df9f67b89be22961dcd1f [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;
Winson Chungbaa7b722017-03-03 21:33:44 -080026import android.content.Context;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080027import android.graphics.Rect;
Wale Ogunwale2ba71292016-05-05 09:16:47 -070028import android.os.Handler;
Robert Carrf9aa2a92016-04-26 14:22:19 -070029import android.os.IBinder;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080030import android.os.Debug;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080031import android.util.ArrayMap;
32import android.util.Slog;
Winson Chungbaa7b722017-03-03 21:33:44 -080033import android.view.animation.AnimationUtils;
34import android.view.animation.Interpolator;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080035import android.view.animation.LinearInterpolator;
Robert Carrf9aa2a92016-04-26 14:22:19 -070036import android.view.WindowManagerInternal;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080037
38/**
39 * Enables animating bounds of objects.
40 *
41 * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
42 * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
43 * relaunching it would cause poorer experience), these class provides a way to directly animate
44 * the bounds of the resized object.
45 *
46 * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
Wale Ogunwale2ba71292016-05-05 09:16:47 -070047 *
48 * NOTE: All calls to methods in this class should be done on the UI thread
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080049 */
50public class BoundsAnimationController {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080051 private static final boolean DEBUG_LOCAL = false;
52 private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;
53 private static final String TAG = TAG_WITH_CLASS_NAME || DEBUG_LOCAL
54 ? "BoundsAnimationController" : TAG_WM;
Wale Ogunwalece144522016-02-05 22:51:01 -080055 private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080056
Winson Chungbaa7b722017-03-03 21:33:44 -080057 private static final int DEFAULT_TRANSITION_DURATION = 425;
58
Wale Ogunwalece144522016-02-05 22:51:01 -080059 // Only accessed on UI thread.
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080060 private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
61
Wale Ogunwale2ba71292016-05-05 09:16:47 -070062 private final class AppTransitionNotifier
63 extends WindowManagerInternal.AppTransitionListener implements Runnable {
Robert Carrf9aa2a92016-04-26 14:22:19 -070064
Wale Ogunwale2ba71292016-05-05 09:16:47 -070065 public void onAppTransitionCancelledLocked() {
66 animationFinished();
67 }
68 public void onAppTransitionFinishedLocked(IBinder token) {
69 animationFinished();
70 }
71 private void animationFinished() {
72 if (mFinishAnimationAfterTransition) {
73 mHandler.removeCallbacks(this);
74 // This might end up calling into activity manager which will be bad since we have the
75 // window manager lock held at this point. Post a message to take care of the processing
76 // so we don't deadlock.
77 mHandler.post(this);
78 }
79 }
80
81 @Override
82 public void run() {
83 for (int i = 0; i < mRunningAnimations.size(); i++) {
84 final BoundsAnimator b = mRunningAnimations.valueAt(i);
85 b.onAnimationEnd(null);
86 }
87 }
88 }
89
90 private final Handler mHandler;
Robert Carrf9aa2a92016-04-26 14:22:19 -070091 private final AppTransition mAppTransition;
Wale Ogunwale2ba71292016-05-05 09:16:47 -070092 private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier();
Winson Chungbaa7b722017-03-03 21:33:44 -080093 private final Interpolator mFastOutSlowInInterpolator;
Robert Carrf9aa2a92016-04-26 14:22:19 -070094 private boolean mFinishAnimationAfterTransition = false;
95
Winson Chungbaa7b722017-03-03 21:33:44 -080096 BoundsAnimationController(Context context, AppTransition transition, Handler handler) {
Wale Ogunwale2ba71292016-05-05 09:16:47 -070097 mHandler = handler;
Robert Carrf9aa2a92016-04-26 14:22:19 -070098 mAppTransition = transition;
99 mAppTransition.registerListenerLocked(mAppTransitionNotifier);
Winson Chungbaa7b722017-03-03 21:33:44 -0800100 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
101 com.android.internal.R.interpolator.fast_out_slow_in);
Robert Carrf9aa2a92016-04-26 14:22:19 -0700102 }
103
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800104 private final class BoundsAnimator extends ValueAnimator
105 implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
106 private final AnimateBoundsUser mTarget;
Winson Chung84a38342016-11-08 16:15:10 -0800107 private final Rect mFrom = new Rect();
108 private final Rect mTo = new Rect();
Robert Carr0d00c2e2016-02-29 17:45:02 -0800109 private final Rect mTmpRect = new Rect();
110 private final Rect mTmpTaskBounds = new Rect();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800111 private final boolean mMoveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -0800112 // True if this this animation was cancelled and will be replaced the another animation from
113 // the same {@link #AnimateBoundsUser} target.
114 private boolean mWillReplace;
115 // True to true if this animation replaced a previous animation of the same
116 // {@link #AnimateBoundsUser} target.
117 private final boolean mReplacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800118
Robert Carr0d00c2e2016-02-29 17:45:02 -0800119 // Depending on whether we are animating from
120 // a smaller to a larger size
121 private final int mFrozenTaskWidth;
122 private final int mFrozenTaskHeight;
123
Wale Ogunwalece144522016-02-05 22:51:01 -0800124 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
125 boolean moveToFullScreen, boolean replacement) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800126 super();
127 mTarget = target;
Winson Chung84a38342016-11-08 16:15:10 -0800128 mFrom.set(from);
129 mTo.set(to);
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800130 mMoveToFullScreen = moveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -0800131 mReplacement = replacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800132 addUpdateListener(this);
133 addListener(this);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800134
135 // If we are animating from smaller to larger, we want to change the task bounds
136 // to their final size immediately so we can use scaling to make the window
137 // larger. Likewise if we are going from bigger to smaller, we want to wait until
138 // the end so we don't have to upscale from the smaller finished size.
139 if (animatingToLargerSize()) {
140 mFrozenTaskWidth = mTo.width();
141 mFrozenTaskHeight = mTo.height();
142 } else {
143 mFrozenTaskWidth = mFrom.width();
144 mFrozenTaskHeight = mFrom.height();
145 }
146 }
147
148 boolean animatingToLargerSize() {
149 if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
150 return false;
151 }
152 return true;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800153 }
154
155 @Override
156 public void onAnimationUpdate(ValueAnimator animation) {
157 final float value = (Float) animation.getAnimatedValue();
158 final float remains = 1 - value;
Wale Ogunwalece144522016-02-05 22:51:01 -0800159 mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
160 mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
161 mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
162 mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800163 if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
164 + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
165 + " remains=" + remains);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800166
Robert Carrc7294602016-05-13 11:32:05 -0700167 mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
168 mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800169
Robert Carrc7294602016-05-13 11:32:05 -0700170 if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800171 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
172 // any further animation.
173 animation.cancel();
174 }
175 }
176
177
178 @Override
179 public void onAnimationStart(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800180 if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
181 + " mReplacement=" + mReplacement);
Robert Carrf9aa2a92016-04-26 14:22:19 -0700182 mFinishAnimationAfterTransition = false;
Robert Carr69016e32016-04-18 11:52:21 -0700183 // Ensure that we have prepared the target for animation before
184 // we trigger any size changes, so it can swap surfaces
185 // in to appropriate modes, or do as it wishes otherwise.
Wale Ogunwalece144522016-02-05 22:51:01 -0800186 if (!mReplacement) {
Robert Carr7e4c90e2017-02-15 19:52:38 -0800187 mTarget.onAnimationStart(mMoveToFullScreen);
Wale Ogunwalece144522016-02-05 22:51:01 -0800188 }
Robert Carr1ca6a332016-04-11 18:00:43 -0700189
Robert Carr69016e32016-04-18 11:52:21 -0700190 // Immediately update the task bounds if they have to become larger, but preserve
191 // the starting position so we don't jump at the beginning of the animation.
Robert Carr1ca6a332016-04-11 18:00:43 -0700192 if (animatingToLargerSize()) {
Robert Carr69016e32016-04-18 11:52:21 -0700193 mTmpRect.set(mFrom.left, mFrom.top,
194 mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight);
195 mTarget.setPinnedStackSize(mFrom, mTmpRect);
Robert Carr1ca6a332016-04-11 18:00:43 -0700196 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800197 }
198
199 @Override
200 public void onAnimationEnd(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800201 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
202 + " mMoveToFullScreen=" + mMoveToFullScreen + " mWillReplace=" + mWillReplace);
Jaewan Kimb0033642016-04-22 18:41:37 +0900203
Robert Carrf9aa2a92016-04-26 14:22:19 -0700204 // There could be another animation running. For example in the
205 // move to fullscreen case, recents will also be closing while the
206 // previous task will be taking its place in the fullscreen stack.
207 // we have to ensure this is completed before we finish the animation
208 // and take our place in the fullscreen stack.
209 if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
210 mFinishAnimationAfterTransition = true;
211 return;
212 }
213
Robert Carrc7294602016-05-13 11:32:05 -0700214 finishAnimation();
215
216 mTarget.setPinnedStackSize(mTo, null);
Wale Ogunwalece144522016-02-05 22:51:01 -0800217 if (mMoveToFullScreen && !mWillReplace) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800218 mTarget.moveToFullscreen();
219 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800220 }
221
222 @Override
223 public void onAnimationCancel(Animator animation) {
224 finishAnimation();
225 }
226
Wale Ogunwalece144522016-02-05 22:51:01 -0800227 @Override
228 public void cancel() {
229 mWillReplace = true;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800230 if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
Wale Ogunwalece144522016-02-05 22:51:01 -0800231 super.cancel();
232 }
233
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800234 /** Returns true if the animation target is the same as the input bounds. */
235 public boolean isAnimatingTo(Rect bounds) {
236 return mTo.equals(bounds);
237 }
238
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800239 private void finishAnimation() {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800240 if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
241 + " callers" + Debug.getCallers(2));
Wale Ogunwalece144522016-02-05 22:51:01 -0800242 if (!mWillReplace) {
243 mTarget.onAnimationEnd();
244 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800245 removeListener(this);
246 removeUpdateListener(this);
247 mRunningAnimations.remove(mTarget);
248 }
249
250 @Override
251 public void onAnimationRepeat(Animator animation) {
252
253 }
254 }
255
256 public interface AnimateBoundsUser {
257 /**
258 * Asks the target to directly (without any intermediate steps, like scheduling animation)
259 * resize its bounds.
260 *
261 * @return Whether the target still wants to be animated and successfully finished the
262 * operation. If it returns false, the animation will immediately be cancelled. The target
263 * should return false when something abnormal happened, e.g. it was completely removed
264 * from the hierarchy and is not valid anymore.
265 */
266 boolean setSize(Rect bounds);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800267 /**
268 * Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
269 * to allow for more flexibility during resizing. Only
270 * works for the pinned stack at the moment.
271 */
272 boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800273
Robert Carr7e4c90e2017-02-15 19:52:38 -0800274 void onAnimationStart(boolean toFullscreen);
Wale Ogunwalece144522016-02-05 22:51:01 -0800275
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800276 /**
Wale Ogunwalece144522016-02-05 22:51:01 -0800277 * Callback for the target to inform it that the animation has ended, so it can do some
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800278 * necessary cleanup.
279 */
Wale Ogunwalece144522016-02-05 22:51:01 -0800280 void onAnimationEnd();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800281
282 void moveToFullscreen();
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800283 }
284
Winson Chung55893332017-02-17 17:13:10 -0800285 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration,
286 boolean moveToFullscreen) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800287 final BoundsAnimator existing = mRunningAnimations.get(target);
Wale Ogunwalece144522016-02-05 22:51:01 -0800288 final boolean replacing = existing != null;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800289
290 if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
291 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing);
292
Wale Ogunwalece144522016-02-05 22:51:01 -0800293 if (replacing) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800294 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 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800301 existing.cancel();
302 }
Wale Ogunwalece144522016-02-05 22:51:01 -0800303 final BoundsAnimator animator =
304 new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800305 mRunningAnimations.put(target, animator);
306 animator.setFloatValues(0f, 1f);
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700307 animator.setDuration((animationDuration != -1 ? animationDuration
Winson Chungbaa7b722017-03-03 21:33:44 -0800308 : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
309 animator.setInterpolator(mFastOutSlowInInterpolator);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800310 animator.start();
311 }
312}