1 /* 2 * Copyright (C) 2017 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 17 package com.android.server.wm; 18 19 import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION; 20 import static com.android.server.wm.AnimationSpecProto.WINDOW; 21 import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; 22 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE; 23 24 import android.graphics.Insets; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.os.SystemClock; 28 import android.util.proto.ProtoOutputStream; 29 import android.view.SurfaceControl; 30 import android.view.SurfaceControl.Transaction; 31 import android.view.animation.Animation; 32 import android.view.animation.AnimationSet; 33 import android.view.animation.Interpolator; 34 import android.view.animation.Transformation; 35 import android.view.animation.TranslateAnimation; 36 37 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; 38 39 import java.io.PrintWriter; 40 41 /** 42 * Animation spec for regular window animations. 43 */ 44 public class WindowAnimationSpec implements AnimationSpec { 45 46 private Animation mAnimation; 47 private final Point mPosition = new Point(); 48 private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new); 49 private final boolean mCanSkipFirstFrame; 50 private final boolean mIsAppAnimation; 51 private final Rect mRootTaskBounds = new Rect(); 52 private int mRootTaskClipMode; 53 private final Rect mTmpRect = new Rect(); 54 private final float mWindowCornerRadius; 55 WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame, float windowCornerRadius)56 public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame, 57 float windowCornerRadius) { 58 this(animation, position, null /* rootTaskBounds */, canSkipFirstFrame, ROOT_TASK_CLIP_NONE, 59 false /* isAppAnimation */, windowCornerRadius); 60 } 61 WindowAnimationSpec(Animation animation, Point position, Rect rootTaskBounds, boolean canSkipFirstFrame, int rootTaskClipMode, boolean isAppAnimation, float windowCornerRadius)62 public WindowAnimationSpec(Animation animation, Point position, Rect rootTaskBounds, 63 boolean canSkipFirstFrame, int rootTaskClipMode, boolean isAppAnimation, 64 float windowCornerRadius) { 65 mAnimation = animation; 66 if (position != null) { 67 mPosition.set(position.x, position.y); 68 } 69 mWindowCornerRadius = windowCornerRadius; 70 mCanSkipFirstFrame = canSkipFirstFrame; 71 mIsAppAnimation = isAppAnimation; 72 mRootTaskClipMode = rootTaskClipMode; 73 if (rootTaskBounds != null) { 74 mRootTaskBounds.set(rootTaskBounds); 75 } 76 } 77 78 @Override asWindowAnimationSpec()79 public WindowAnimationSpec asWindowAnimationSpec() { 80 return this; 81 } 82 83 @Override getShowWallpaper()84 public boolean getShowWallpaper() { 85 return mAnimation.getShowWallpaper(); 86 } 87 88 @Override getShowBackground()89 public boolean getShowBackground() { 90 return mAnimation.getShowBackdrop(); 91 } 92 93 @Override getBackgroundColor()94 public int getBackgroundColor() { 95 return mAnimation.getBackdropColor(); 96 } 97 98 /** 99 * @return If a window animation has outsets applied to it. 100 * @see Animation#hasExtension() 101 */ hasExtension()102 public boolean hasExtension() { 103 return mAnimation.hasExtension(); 104 } 105 106 @Override getDuration()107 public long getDuration() { 108 return mAnimation.computeDurationHint(); 109 } 110 getRootTaskBounds()111 public Rect getRootTaskBounds() { 112 return mRootTaskBounds; 113 } 114 getAnimation()115 public Animation getAnimation() { 116 return mAnimation; 117 } 118 119 @Override apply(Transaction t, SurfaceControl leash, long currentPlayTime)120 public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) { 121 final TmpValues tmp = mThreadLocalTmps.get(); 122 tmp.transformation.clear(); 123 mAnimation.getTransformation(currentPlayTime, tmp.transformation); 124 tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); 125 t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats); 126 t.setAlpha(leash, tmp.transformation.getAlpha()); 127 128 boolean cropSet = false; 129 if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) { 130 if (tmp.transformation.hasClipRect()) { 131 final Rect clipRect = tmp.transformation.getClipRect(); 132 accountForExtension(tmp.transformation, clipRect); 133 t.setWindowCrop(leash, clipRect); 134 cropSet = true; 135 } 136 } else { 137 mTmpRect.set(mRootTaskBounds); 138 if (tmp.transformation.hasClipRect()) { 139 mTmpRect.intersect(tmp.transformation.getClipRect()); 140 } 141 accountForExtension(tmp.transformation, mTmpRect); 142 t.setWindowCrop(leash, mTmpRect); 143 cropSet = true; 144 } 145 146 // We can only apply rounded corner if a crop is set, as otherwise the value is meaningless, 147 // since it doesn't have anything it's relative to. 148 if (cropSet && mAnimation.hasRoundedCorners() && mWindowCornerRadius > 0) { 149 t.setCornerRadius(leash, mWindowCornerRadius); 150 } 151 } 152 accountForExtension(Transformation transformation, Rect clipRect)153 private void accountForExtension(Transformation transformation, Rect clipRect) { 154 Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE); 155 if (!extensionInsets.equals(Insets.NONE)) { 156 // Extend the surface to allow for the edge extension to be visible 157 clipRect.inset(extensionInsets); 158 } 159 } 160 161 @Override calculateStatusBarTransitionStartTime()162 public long calculateStatusBarTransitionStartTime() { 163 TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation); 164 165 if (openTranslateAnimation != null) { 166 if (openTranslateAnimation.isXAxisTransition() 167 && openTranslateAnimation.isFullWidthTranslate()) { 168 // On X axis transitions that are fullscreen (heuristic for task like transitions) 169 // we want the status bar to animate right in the middle of the translation when 170 // the windows/tasks have each moved half way across. 171 float t = findMiddleOfTranslationFraction(openTranslateAnimation.getInterpolator()); 172 173 return SystemClock.uptimeMillis() 174 + openTranslateAnimation.getStartOffset() 175 + (long) (openTranslateAnimation.getDuration() * t) 176 - (long) (STATUS_BAR_TRANSITION_DURATION * 0.5); 177 } else { 178 // Some interpolators are extremely quickly mostly finished, but not completely. For 179 // our purposes, we need to find the fraction for which their interpolator is mostly 180 // there, and use that value for the calculation. 181 float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator()); 182 183 return SystemClock.uptimeMillis() 184 + openTranslateAnimation.getStartOffset() 185 + (long) (openTranslateAnimation.getDuration() * t) 186 - STATUS_BAR_TRANSITION_DURATION; 187 } 188 } else { 189 return SystemClock.uptimeMillis(); 190 } 191 } 192 193 @Override canSkipFirstFrame()194 public boolean canSkipFirstFrame() { 195 return mCanSkipFirstFrame; 196 } 197 198 @Override needsEarlyWakeup()199 public boolean needsEarlyWakeup() { 200 return mIsAppAnimation; 201 } 202 203 @Override dump(PrintWriter pw, String prefix)204 public void dump(PrintWriter pw, String prefix) { 205 pw.print(prefix); pw.println(mAnimation); 206 } 207 208 @Override dumpDebugInner(ProtoOutputStream proto)209 public void dumpDebugInner(ProtoOutputStream proto) { 210 final long token = proto.start(WINDOW); 211 proto.write(ANIMATION, mAnimation.toString()); 212 proto.end(token); 213 } 214 215 /** 216 * Tries to find a {@link TranslateAnimation} inside the {@code animation}. 217 * 218 * @return the found animation, {@code null} otherwise 219 */ findTranslateAnimation(Animation animation)220 private static TranslateAnimation findTranslateAnimation(Animation animation) { 221 if (animation instanceof TranslateAnimation) { 222 return (TranslateAnimation) animation; 223 } else if (animation instanceof AnimationSet) { 224 AnimationSet set = (AnimationSet) animation; 225 for (int i = 0; i < set.getAnimations().size(); i++) { 226 Animation a = set.getAnimations().get(i); 227 if (a instanceof TranslateAnimation) { 228 return (TranslateAnimation) a; 229 } 230 } 231 } 232 return null; 233 } 234 235 /** 236 * Finds the fraction of the animation's duration at which the transition is almost done with a 237 * maximal error of 0.01 when it is animated with {@code interpolator}. 238 */ findAlmostThereFraction(Interpolator interpolator)239 private static float findAlmostThereFraction(Interpolator interpolator) { 240 return findInterpolationAdjustedTargetFraction(interpolator, 0.99f, 0.01f); 241 } 242 243 /** 244 * Finds the fraction of the animation's duration at which the transition is spacially half way 245 * done with a maximal error of 0.01 when it is animated with {@code interpolator}. 246 */ findMiddleOfTranslationFraction(Interpolator interpolator)247 private float findMiddleOfTranslationFraction(Interpolator interpolator) { 248 return findInterpolationAdjustedTargetFraction(interpolator, 0.5f, 0.01f); 249 } 250 251 /** 252 * Binary searches for a {@code val} such that there exists an {@code -0.01 < epsilon < 0.01} 253 * for which {@code interpolator(val + epsilon) > target}. 254 */ findInterpolationAdjustedTargetFraction( Interpolator interpolator, float target, float epsilon)255 private static float findInterpolationAdjustedTargetFraction( 256 Interpolator interpolator, float target, float epsilon) { 257 float val = 0.5f; 258 float adj = 0.25f; 259 260 while (adj >= epsilon) { 261 if (interpolator.getInterpolation(val) < target) { 262 val += adj; 263 } else { 264 val -= adj; 265 } 266 adj /= 2; 267 } 268 269 return val; 270 } 271 272 private static class TmpValues { 273 final Transformation transformation = new Transformation(); 274 final float[] floats = new float[9]; 275 } 276 } 277