blob: 0678ca2de3658e1c53c94d3e52e124b82c7142b7 [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;
27import android.util.ArrayMap;
28import android.util.Slog;
29import android.view.animation.LinearInterpolator;
30
31/**
32 * Enables animating bounds of objects.
33 *
34 * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
35 * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
36 * relaunching it would cause poorer experience), these class provides a way to directly animate
37 * the bounds of the resized object.
38 *
39 * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
40 */
41public class BoundsAnimationController {
42 private static final String TAG = TAG_WITH_CLASS_NAME ? "BoundsAnimationController" : TAG_WM;
Wale Ogunwalece144522016-02-05 22:51:01 -080043 private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080044
Wale Ogunwalece144522016-02-05 22:51:01 -080045 // Only accessed on UI thread.
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080046 private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
47
48 private final class BoundsAnimator extends ValueAnimator
49 implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
50 private final AnimateBoundsUser mTarget;
51 private final Rect mFrom;
52 private final Rect mTo;
53 private final Rect mTmpRect;
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -080054 private final boolean mMoveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -080055 // True if this this animation was cancelled and will be replaced the another animation from
56 // the same {@link #AnimateBoundsUser} target.
57 private boolean mWillReplace;
58 // True to true if this animation replaced a previous animation of the same
59 // {@link #AnimateBoundsUser} target.
60 private final boolean mReplacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080061
Wale Ogunwalece144522016-02-05 22:51:01 -080062 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to,
63 boolean moveToFullScreen, boolean replacement) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080064 super();
65 mTarget = target;
66 mFrom = from;
67 mTo = to;
68 mTmpRect = new Rect();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -080069 mMoveToFullScreen = moveToFullScreen;
Wale Ogunwalece144522016-02-05 22:51:01 -080070 mReplacement = replacement;
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080071 addUpdateListener(this);
72 addListener(this);
73 }
74
75 @Override
76 public void onAnimationUpdate(ValueAnimator animation) {
77 final float value = (Float) animation.getAnimatedValue();
78 final float remains = 1 - value;
Wale Ogunwalece144522016-02-05 22:51:01 -080079 mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
80 mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
81 mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
82 mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080083 if (DEBUG_ANIM) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + ", mBounds="
84 + mTmpRect + ", from=" + mFrom + ", mTo=" + mTo + ", value=" + value
85 + ", remains=" + remains);
86 if (!mTarget.setSize(mTmpRect)) {
87 // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
88 // any further animation.
89 animation.cancel();
90 }
91 }
92
93
94 @Override
95 public void onAnimationStart(Animator animation) {
Wale Ogunwalece144522016-02-05 22:51:01 -080096 if (!mReplacement) {
97 mTarget.onAnimationStart();
98 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -080099 }
100
101 @Override
102 public void onAnimationEnd(Animator animation) {
103 finishAnimation();
Wale Ogunwalece144522016-02-05 22:51:01 -0800104 if (mMoveToFullScreen && !mWillReplace) {
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800105 mTarget.moveToFullscreen();
106 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800107 }
108
109 @Override
110 public void onAnimationCancel(Animator animation) {
111 finishAnimation();
112 }
113
Wale Ogunwalece144522016-02-05 22:51:01 -0800114 @Override
115 public void cancel() {
116 mWillReplace = true;
117 super.cancel();
118 }
119
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800120 private void finishAnimation() {
Wale Ogunwalece144522016-02-05 22:51:01 -0800121 if (!mWillReplace) {
122 mTarget.onAnimationEnd();
123 }
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800124 removeListener(this);
125 removeUpdateListener(this);
126 mRunningAnimations.remove(mTarget);
127 }
128
129 @Override
130 public void onAnimationRepeat(Animator animation) {
131
132 }
133 }
134
135 public interface AnimateBoundsUser {
136 /**
137 * Asks the target to directly (without any intermediate steps, like scheduling animation)
138 * resize its bounds.
139 *
140 * @return Whether the target still wants to be animated and successfully finished the
141 * operation. If it returns false, the animation will immediately be cancelled. The target
142 * should return false when something abnormal happened, e.g. it was completely removed
143 * from the hierarchy and is not valid anymore.
144 */
145 boolean setSize(Rect bounds);
146
Wale Ogunwalece144522016-02-05 22:51:01 -0800147 void onAnimationStart();
148
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800149 /**
Wale Ogunwalece144522016-02-05 22:51:01 -0800150 * Callback for the target to inform it that the animation has ended, so it can do some
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800151 * necessary cleanup.
152 */
Wale Ogunwalece144522016-02-05 22:51:01 -0800153 void onAnimationEnd();
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800154
155 void moveToFullscreen();
156
157 void getFullScreenBounds(Rect bounds);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800158 }
159
Filip Gruszczynskic17d8b72016-02-03 16:52:59 -0800160 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to) {
161 boolean moveToFullscreen = false;
162 if (to == null) {
163 to = new Rect();
164 target.getFullScreenBounds(to);
165 moveToFullscreen = true;
166 }
167
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800168 final BoundsAnimator existing = mRunningAnimations.get(target);
Wale Ogunwalece144522016-02-05 22:51:01 -0800169 final boolean replacing = existing != null;
170 if (replacing) {
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800171 existing.cancel();
172 }
Wale Ogunwalece144522016-02-05 22:51:01 -0800173 final BoundsAnimator animator =
174 new BoundsAnimator(target, from, to, moveToFullscreen, replacing);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800175 mRunningAnimations.put(target, animator);
176 animator.setFloatValues(0f, 1f);
Wale Ogunwalece144522016-02-05 22:51:01 -0800177 animator.setDuration(DEFAULT_APP_TRANSITION_DURATION * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
Filip Gruszczynski84fa3352016-01-25 16:28:49 -0800178 animator.setInterpolator(new LinearInterpolator());
179 animator.start();
180 }
181}