blob: f0efebed0747c3417f9f98b9d009d96e66cfcb41 [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;
57 private final Rect mTmpRect;
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -080058 private final boolean mMoveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -080059 // True if this this animation was cancelled and will be replaced the another animation from
60 // the same {@link #AnimateBoundsUser} target.
61 private boolean mWillReplace;
62 // True to true if this animation replaced a previous animation of the same
63 // {@link #AnimateBoundsUser} target.
64 private final boolean mReplacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080065
Wale Ogunwalece144522016-02-05 22:51:01 -080066 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
67 boolean moveToFullScreen, boolean replacement) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080068 super();
69 mTarget = target;
70 mFrom = from;
71 mTo = to;
72 mTmpRect = new Rect();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -080073 mMoveToFullScreen = moveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -080074 mReplacement = replacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080075 addUpdateListener(this);
76 addListener(this);
77 }
78
79 @Override
80 public void onAnimationUpdate(ValueAnimator animation) {
81 final float value = (Float) animation.getAnimatedValue();
82 final float remains = 1 - value;
Wale Ogunwalece144522016-02-05 22:51:01 -080083 mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
84 mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
85 mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
86 mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
Wale Ogunwale5658e4b2016-02-12 12:22:19 -080087 if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
88 + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
89 + " remains=" + remains);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080090 if (!mTarget.setSize(mTmpRect)) {
91 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
92 // any further animation.
93 animation.cancel();
94 }
95 }
96
97
98 @Override
99 public void onAnimationStart(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800100 if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
101 + " mReplacement=" + mReplacement);
Wale Ogunwalece144522016-02-05 22:51:01 -0800102 if (!mReplacement) {
103 mTarget.onAnimationStart();
104 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800105 }
106
107 @Override
108 public void onAnimationEnd(Animator animation) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800109 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
110 + " mMoveToFullScreen=" + mMoveToFullScreen + " mWillReplace=" + mWillReplace);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800111 finishAnimation();
Wale Ogunwalece144522016-02-05 22:51:01 -0800112 if (mMoveToFullScreen && !mWillReplace) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800113 mTarget.moveToFullscreen();
114 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800115 }
116
117 @Override
118 public void onAnimationCancel(Animator animation) {
119 finishAnimation();
120 }
121
Wale Ogunwalece144522016-02-05 22:51:01 -0800122 @Override
123 public void cancel() {
124 mWillReplace = true;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800125 if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget);
Wale Ogunwalece144522016-02-05 22:51:01 -0800126 super.cancel();
127 }
128
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800129 /** Returns true if the animation target is the same as the input bounds. */
130 public boolean isAnimatingTo(Rect bounds) {
131 return mTo.equals(bounds);
132 }
133
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800134 private void finishAnimation() {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800135 if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
136 + " callers" + Debug.getCallers(2));
Wale Ogunwalece144522016-02-05 22:51:01 -0800137 if (!mWillReplace) {
138 mTarget.onAnimationEnd();
139 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800140 removeListener(this);
141 removeUpdateListener(this);
142 mRunningAnimations.remove(mTarget);
143 }
144
145 @Override
146 public void onAnimationRepeat(Animator animation) {
147
148 }
149 }
150
151 public interface AnimateBoundsUser {
152 /**
153 * Asks the target to directly (without any intermediate steps, like scheduling animation)
154 * resize its bounds.
155 *
156 * @return Whether the target still wants to be animated and successfully finished the
157 * operation. If it returns false, the animation will immediately be cancelled. The target
158 * should return false when something abnormal happened, e.g. it was completely removed
159 * from the hierarchy and is not valid anymore.
160 */
161 boolean setSize(Rect bounds);
162
Wale Ogunwalece144522016-02-05 22:51:01 -0800163 void onAnimationStart();
164
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800165 /**
Wale Ogunwalece144522016-02-05 22:51:01 -0800166 * Callback for the target to inform it that the animation has ended, so it can do some
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800167 * necessary cleanup.
168 */
Wale Ogunwalece144522016-02-05 22:51:01 -0800169 void onAnimationEnd();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800170
171 void moveToFullscreen();
172
173 void getFullScreenBounds(Rect bounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800174 }
175
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800176 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to) {
177 boolean moveToFullscreen = false;
178 if (to == null) {
179 to = new Rect();
180 target.getFullScreenBounds(to);
181 moveToFullscreen = true;
182 }
183
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800184 final BoundsAnimator existing = mRunningAnimations.get(target);
Wale Ogunwalece144522016-02-05 22:51:01 -0800185 final boolean replacing = existing != null;
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800186
187 if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
188 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing);
189
Wale Ogunwalece144522016-02-05 22:51:01 -0800190 if (replacing) {
Wale Ogunwale5658e4b2016-02-12 12:22:19 -0800191 if (existing.isAnimatingTo(to)) {
192 // Just les the current animation complete if it has the same destination as the
193 // one we are trying to start.
194 if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
195 + " ignoring...");
196 return;
197 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800198 existing.cancel();
199 }
Wale Ogunwalece144522016-02-05 22:51:01 -0800200 final BoundsAnimator animator =
201 new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800202 mRunningAnimations.put(target, animator);
203 animator.setFloatValues(0f, 1f);
Wale Ogunwalece144522016-02-05 22:51:01 -0800204 animator.setDuration(DEFAULT_APP_TRANSITION_DURATION * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800205 animator.setInterpolator(new LinearInterpolator());
206 animator.start();
207 }
208}