BoundsAnimationController.java revision b003364bc7e31e7f85865ef298d399ed61c372a2
1/* 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.os.Debug; 28import 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 { 43 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; 47 private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1; 48 49 // Only accessed on UI thread. 50 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 = new Rect(); 58 private final Rect mTmpTaskBounds = new Rect(); 59 private final boolean mMoveToFullScreen; 60 // 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; 66 67 // 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 72 BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, 73 boolean moveToFullScreen, boolean replacement) { 74 super(); 75 mTarget = target; 76 mFrom = from; 77 mTo = to; 78 mMoveToFullScreen = moveToFullScreen; 79 mReplacement = replacement; 80 addUpdateListener(this); 81 addListener(this); 82 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; 101 } 102 103 @Override 104 public void onAnimationUpdate(ValueAnimator animation) { 105 final float value = (Float) animation.getAnimatedValue(); 106 final float remains = 1 - value; 107 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); 111 if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds=" 112 + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value 113 + " remains=" + remains); 114 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)) { 121 // 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) { 130 if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget 131 + " mReplacement=" + mReplacement); 132 // 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. 135 if (!mReplacement) { 136 mTarget.onAnimationStart(); 137 } 138 139 // 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. 141 if (animatingToLargerSize()) { 142 mTmpRect.set(mFrom.left, mFrom.top, 143 mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight); 144 mTarget.setPinnedStackSize(mFrom, mTmpRect); 145 } 146 } 147 148 @Override 149 public void onAnimationEnd(Animator animation) { 150 if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget 151 + " mMoveToFullScreen=" + mMoveToFullScreen + " mWillReplace=" + mWillReplace); 152 153 finishAnimation(); 154 if (mMoveToFullScreen && !mWillReplace) { 155 mTarget.moveToFullscreen(); 156 } 157 } 158 159 @Override 160 public void onAnimationCancel(Animator animation) { 161 finishAnimation(); 162 } 163 164 @Override 165 public void cancel() { 166 mWillReplace = true; 167 if (DEBUG) Slog.d(TAG, "cancel: willReplace mTarget=" + mTarget); 168 super.cancel(); 169 } 170 171 /** 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 176 private void finishAnimation() { 177 if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget 178 + " callers" + Debug.getCallers(2)); 179 if (!mWillReplace) { 180 mTarget.onAnimationEnd(); 181 } 182 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); 204 /** 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); 210 211 void onAnimationStart(); 212 213 /** 214 * Callback for the target to inform it that the animation has ended, so it can do some 215 * necessary cleanup. 216 */ 217 void onAnimationEnd(); 218 219 void moveToFullscreen(); 220 221 void getFullScreenBounds(Rect bounds); 222 } 223 224 void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration) { 225 boolean moveToFullscreen = false; 226 if (to == null) { 227 to = new Rect(); 228 target.getFullScreenBounds(to); 229 moveToFullscreen = true; 230 } 231 232 final BoundsAnimator existing = mRunningAnimations.get(target); 233 final boolean replacing = existing != null; 234 235 if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to 236 + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing); 237 238 if (replacing) { 239 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 } 246 existing.cancel(); 247 } 248 final BoundsAnimator animator = 249 new BoundsAnimator(target, from, to, moveToFullscreen, replacing); 250 mRunningAnimations.put(target, animator); 251 animator.setFloatValues(0f, 1f); 252 animator.setDuration((animationDuration != -1 ? animationDuration 253 : DEFAULT_APP_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR); 254 animator.setInterpolator(new LinearInterpolator()); 255 animator.start(); 256 } 257} 258