blob: debb38257817485b8080eb24014019f89b6195c5 [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 Ogunwale5658e4b2016-02-12 12:22:19 -080027import android.os.Debug;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080028import android.util.ArrayMap;
29import android.util.Slog;
30import android.view.animation.LinearInterpolator;
31
32/**
33 * Enables animating bounds of objects.
34 *
35 * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
36 * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
37 * relaunching it would cause poorer experience), these class provides a way to directly animate
38 * the bounds of the resized object.
39 *
40 * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
41 */
42public class BoundsAnimationController {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080043 private static final boolean DEBUG_LOCAL = false;
44 private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;
45 private static final String TAG = TAG_WITH_CLASS_NAME || DEBUG_LOCAL
46 ? "BoundsAnimationController" : TAG_WM;
Wale Ogunwalece144522016-02-05 22:51:01 -080047 private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080048
Wale Ogunwalece144522016-02-05 22:51:01 -080049 // Only accessed on UI thread.
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080050 private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
51
52 private final class BoundsAnimator extends ValueAnimator
53 implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
54 private final AnimateBoundsUser mTarget;
55 private final Rect mFrom;
56 private final Rect mTo;
Robert Carr0d00c2e2016-02-29 17:45:02 -080057 private final Rect mTmpRect = new Rect();
58 private final Rect mTmpTaskBounds = new Rect();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -080059 private final boolean mMoveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -080060 // True if this this animation was cancelled and will be replaced the another animation from
61 // the same {@link #AnimateBoundsUser} target.
62 private boolean mWillReplace;
63 // True to true if this animation replaced a previous animation of the same
64 // {@link #AnimateBoundsUser} target.
65 private final boolean mReplacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080066
Robert Carr0d00c2e2016-02-29 17:45:02 -080067 // Depending on whether we are animating from
68 // a smaller to a larger size
69 private final int mFrozenTaskWidth;
70 private final int mFrozenTaskHeight;
71
Wale Ogunwalece144522016-02-05 22:51:01 -080072 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
73 boolean moveToFullScreen, boolean replacement) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080074 super();
75 mTarget = target;
76 mFrom = from;
77 mTo = to;
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -080078 mMoveToFullScreen = moveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -080079 mReplacement = replacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080080 addUpdateListener(this);
81 addListener(this);
Robert Carr0d00c2e2016-02-29 17:45:02 -080082
83 // If we are animating from smaller to larger, we want to change the task bounds
84 // to their final size immediately so we can use scaling to make the window
85 // larger. Likewise if we are going from bigger to smaller, we want to wait until
86 // the end so we don't have to upscale from the smaller finished size.
87 if (animatingToLargerSize()) {
88 mFrozenTaskWidth = mTo.width();
89 mFrozenTaskHeight = mTo.height();
90 } else {
91 mFrozenTaskWidth = mFrom.width();
92 mFrozenTaskHeight = mFrom.height();
93 }
94 }
95
96 boolean animatingToLargerSize() {
97 if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
98 return false;
99 }
100 return true;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800101 }
102
103 @Override
104 public void onAnimationUpdate(ValueAnimator animation) {
105 final float value = (Float) animation.getAnimatedValue();
106 final float remains = 1 - value;
Wale Ogunwalece144522016-02-05 22:51:01 -0800107 mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
108 mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
109 mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
110 mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800111 if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
112 + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
113 + " remains=" + remains);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800114
115 if (remains != 0) {
116 mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
117 mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
118 }
119
120 if (!mTarget.setPinnedStackSize(mTmpRect, remains != 0 ? mTmpTaskBounds : null)) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800121 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
122 // any further animation.
123 animation.cancel();
124 }
125 }
126
127
128 @Override
129 public void onAnimationStart(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800130 if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
131 + " mReplacement=" + mReplacement);
Robert Carr69016e32016-04-18 11:52:21 -0700132 // Ensure that we have prepared the target for animation before
133 // we trigger any size changes, so it can swap surfaces
134 // in to appropriate modes, or do as it wishes otherwise.
Wale Ogunwalece144522016-02-05 22:51:01 -0800135 if (!mReplacement) {
136 mTarget.onAnimationStart();
137 }
Robert Carr1ca6a332016-04-11 18:00:43 -0700138
Robert Carr69016e32016-04-18 11:52:21 -0700139 // Immediately update the task bounds if they have to become larger, but preserve
140 // the starting position so we don't jump at the beginning of the animation.
Robert Carr1ca6a332016-04-11 18:00:43 -0700141 if (animatingToLargerSize()) {
Robert Carr69016e32016-04-18 11:52:21 -0700142 mTmpRect.set(mFrom.left, mFrom.top,
143 mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight);
144 mTarget.setPinnedStackSize(mFrom, mTmpRect);
Robert Carr1ca6a332016-04-11 18:00:43 -0700145 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800146 }
147
148 @Override
149 public void onAnimationEnd(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800150 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
151 + " mMoveToFullScreen=" + mMoveToFullScreen + " mWillReplace=" + mWillReplace);
Jaewan Kimb0033642016-04-22 18:41:37 +0900152
153 finishAnimation();
Wale Ogunwalece144522016-02-05 22:51:01 -0800154 if (mMoveToFullScreen && !mWillReplace) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800155 mTarget.moveToFullscreen();
156 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800157 }
158
159 @Override
160 public void onAnimationCancel(Animator animation) {
161 finishAnimation();
162 }
163
Wale Ogunwalece144522016-02-05 22:51:01 -0800164 @Override
165 public void cancel() {
166 mWillReplace = true;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800167 if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
Wale Ogunwalece144522016-02-05 22:51:01 -0800168 super.cancel();
169 }
170
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800171 /** Returns true if the animation target is the same as the input bounds. */
172 public boolean isAnimatingTo(Rect bounds) {
173 return mTo.equals(bounds);
174 }
175
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800176 private void finishAnimation() {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800177 if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
178 + " callers" + Debug.getCallers(2));
Wale Ogunwalece144522016-02-05 22:51:01 -0800179 if (!mWillReplace) {
180 mTarget.onAnimationEnd();
181 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800182 removeListener(this);
183 removeUpdateListener(this);
184 mRunningAnimations.remove(mTarget);
185 }
186
187 @Override
188 public void onAnimationRepeat(Animator animation) {
189
190 }
191 }
192
193 public interface AnimateBoundsUser {
194 /**
195 * Asks the target to directly (without any intermediate steps, like scheduling animation)
196 * resize its bounds.
197 *
198 * @return Whether the target still wants to be animated and successfully finished the
199 * operation. If it returns false, the animation will immediately be cancelled. The target
200 * should return false when something abnormal happened, e.g. it was completely removed
201 * from the hierarchy and is not valid anymore.
202 */
203 boolean setSize(Rect bounds);
Robert Carr0d00c2e2016-02-29 17:45:02 -0800204 /**
205 * Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
206 * to allow for more flexibility during resizing. Only
207 * works for the pinned stack at the moment.
208 */
209 boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800210
Wale Ogunwalece144522016-02-05 22:51:01 -0800211 void onAnimationStart();
212
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800213 /**
Wale Ogunwalece144522016-02-05 22:51:01 -0800214 * Callback for the target to inform it that the animation has ended, so it can do some
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800215 * necessary cleanup.
216 */
Wale Ogunwalece144522016-02-05 22:51:01 -0800217 void onAnimationEnd();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800218
219 void moveToFullscreen();
220
221 void getFullScreenBounds(Rect bounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800222 }
223
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700224 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800225 boolean moveToFullscreen = false;
226 if (to == null) {
227 to = new Rect();
228 target.getFullScreenBounds(to);
229 moveToFullscreen = true;
230 }
231
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800232 final BoundsAnimator existing = mRunningAnimations.get(target);
Wale Ogunwalece144522016-02-05 22:51:01 -0800233 final boolean replacing = existing != null;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800234
235 if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
236 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing);
237
Wale Ogunwalece144522016-02-05 22:51:01 -0800238 if (replacing) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800239 if (existing.isAnimatingTo(to)) {
240 // Just les the current animation complete if it has the same destination as the
241 // one we are trying to start.
242 if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
243 + " ignoring...");
244 return;
245 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800246 existing.cancel();
247 }
Wale Ogunwalece144522016-02-05 22:51:01 -0800248 final BoundsAnimator animator =
249 new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800250 mRunningAnimations.put(target, animator);
251 animator.setFloatValues(0f, 1f);
Wale Ogunwalee75a9ad2016-03-18 20:43:49 -0700252 animator.setDuration((animationDuration != -1 ? animationDuration
253 : DEFAULT_APP_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800254 animator.setInterpolator(new LinearInterpolator());
255 animator.start();
256 }
257}