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.Point; 25 import android.graphics.Rect; 26 import android.os.SystemClock; 27 import android.util.proto.ProtoOutputStream; 28 import android.view.SurfaceControl; 29 import android.view.SurfaceControl.Transaction; 30 import android.view.animation.Animation; 31 import android.view.animation.AnimationSet; 32 import android.view.animation.Interpolator; 33 import android.view.animation.Transformation; 34 import android.view.animation.TranslateAnimation; 35 36 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; 37 38 import java.io.PrintWriter; 39 40 /** 41 * Animation spec for regular window animations. 42 */ 43 public class WindowAnimationSpec implements AnimationSpec { 44 45 private Animation mAnimation; 46 private final Point mPosition = new Point(); 47 private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new); 48 private final boolean mCanSkipFirstFrame; 49 private final boolean mIsAppAnimation; 50 private final Rect mRootTaskBounds = new Rect(); 51 private int mRootTaskClipMode; 52 private final Rect mTmpRect = new Rect(); 53 private final float mWindowCornerRadius; 54 WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame, float windowCornerRadius)55 public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame, 56 float windowCornerRadius) { 57 this(animation, position, null /* rootTaskBounds */, canSkipFirstFrame, ROOT_TASK_CLIP_NONE, 58 false /* isAppAnimation */, windowCornerRadius); 59 } 60 WindowAnimationSpec(Animation animation, Point position, Rect rootTaskBounds, boolean canSkipFirstFrame, int rootTaskClipMode, boolean isAppAnimation, float windowCornerRadius)61 public WindowAnimationSpec(Animation animation, Point position, Rect rootTaskBounds, 62 boolean canSkipFirstFrame, int rootTaskClipMode, boolean isAppAnimation, 63 float windowCornerRadius) { 64 mAnimation = animation; 65 if (position != null) { 66 mPosition.set(position.x, position.y); 67 } 68 mWindowCornerRadius = windowCornerRadius; 69 mCanSkipFirstFrame = canSkipFirstFrame; 70 mIsAppAnimation = isAppAnimation; 71 mRootTaskClipMode = rootTaskClipMode; 72 if (rootTaskBounds != null) { 73 mRootTaskBounds.set(rootTaskBounds); 74 } 75 } 76 77 @Override getShowWallpaper()78 public boolean getShowWallpaper() { 79 return mAnimation.getShowWallpaper(); 80 } 81 82 @Override getDuration()83 public long getDuration() { 84 return mAnimation.computeDurationHint(); 85 } 86 87 @Override apply(Transaction t, SurfaceControl leash, long currentPlayTime)88 public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) { 89 final TmpValues tmp = mThreadLocalTmps.get(); 90 tmp.transformation.clear(); 91 mAnimation.getTransformation(currentPlayTime, tmp.transformation); 92 tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); 93 t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats); 94 t.setAlpha(leash, tmp.transformation.getAlpha()); 95 96 boolean cropSet = false; 97 if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) { 98 if (tmp.transformation.hasClipRect()) { 99 t.setWindowCrop(leash, tmp.transformation.getClipRect()); 100 cropSet = true; 101 } 102 } else { 103 mTmpRect.set(mRootTaskBounds); 104 if (tmp.transformation.hasClipRect()) { 105 mTmpRect.intersect(tmp.transformation.getClipRect()); 106 } 107 t.setWindowCrop(leash, mTmpRect); 108 cropSet = true; 109 } 110 111 // We can only apply rounded corner if a crop is set, as otherwise the value is meaningless, 112 // since it doesn't have anything it's relative to. 113 if (cropSet && mAnimation.hasRoundedCorners() && mWindowCornerRadius > 0) { 114 t.setCornerRadius(leash, mWindowCornerRadius); 115 } 116 } 117 118 @Override calculateStatusBarTransitionStartTime()119 public long calculateStatusBarTransitionStartTime() { 120 TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation); 121 if (openTranslateAnimation != null) { 122 123 // Some interpolators are extremely quickly mostly finished, but not completely. For 124 // our purposes, we need to find the fraction for which ther interpolator is mostly 125 // there, and use that value for the calculation. 126 float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator()); 127 return SystemClock.uptimeMillis() 128 + openTranslateAnimation.getStartOffset() 129 + (long)(openTranslateAnimation.getDuration() * t) 130 - STATUS_BAR_TRANSITION_DURATION; 131 } else { 132 return SystemClock.uptimeMillis(); 133 } 134 } 135 136 @Override canSkipFirstFrame()137 public boolean canSkipFirstFrame() { 138 return mCanSkipFirstFrame; 139 } 140 141 @Override needsEarlyWakeup()142 public boolean needsEarlyWakeup() { 143 return mIsAppAnimation; 144 } 145 146 @Override dump(PrintWriter pw, String prefix)147 public void dump(PrintWriter pw, String prefix) { 148 pw.print(prefix); pw.println(mAnimation); 149 } 150 151 @Override dumpDebugInner(ProtoOutputStream proto)152 public void dumpDebugInner(ProtoOutputStream proto) { 153 final long token = proto.start(WINDOW); 154 proto.write(ANIMATION, mAnimation.toString()); 155 proto.end(token); 156 } 157 158 /** 159 * Tries to find a {@link TranslateAnimation} inside the {@code animation}. 160 * 161 * @return the found animation, {@code null} otherwise 162 */ findTranslateAnimation(Animation animation)163 private static TranslateAnimation findTranslateAnimation(Animation animation) { 164 if (animation instanceof TranslateAnimation) { 165 return (TranslateAnimation) animation; 166 } else if (animation instanceof AnimationSet) { 167 AnimationSet set = (AnimationSet) animation; 168 for (int i = 0; i < set.getAnimations().size(); i++) { 169 Animation a = set.getAnimations().get(i); 170 if (a instanceof TranslateAnimation) { 171 return (TranslateAnimation) a; 172 } 173 } 174 } 175 return null; 176 } 177 178 /** 179 * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which 180 * {@code interpolator(t + eps) > 0.99}. 181 */ findAlmostThereFraction(Interpolator interpolator)182 private static float findAlmostThereFraction(Interpolator interpolator) { 183 float val = 0.5f; 184 float adj = 0.25f; 185 while (adj >= 0.01f) { 186 if (interpolator.getInterpolation(val) < 0.99f) { 187 val += adj; 188 } else { 189 val -= adj; 190 } 191 adj /= 2; 192 } 193 return val; 194 } 195 196 private static class TmpValues { 197 final Transformation transformation = new Transformation(); 198 final float[] floats = new float[9]; 199 } 200 } 201