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.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; 21 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; 22 import static com.android.server.wm.AnimationSpecProto.WINDOW; 23 import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; 24 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 mStackBounds = new Rect(); 52 private int mStackClipMode; 53 private final Rect mTmpRect = new Rect(); 54 55 public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame) { 56 this(animation, position, null /* stackBounds */, canSkipFirstFrame, STACK_CLIP_NONE, 57 false /* isAppAnimation */); 58 } 59 60 public WindowAnimationSpec(Animation animation, Point position, Rect stackBounds, 61 boolean canSkipFirstFrame, int stackClipMode, boolean isAppAnimation) { 62 mAnimation = animation; 63 if (position != null) { 64 mPosition.set(position.x, position.y); 65 } 66 mCanSkipFirstFrame = canSkipFirstFrame; 67 mIsAppAnimation = isAppAnimation; 68 mStackClipMode = stackClipMode; 69 if (stackBounds != null) { 70 mStackBounds.set(stackBounds); 71 } 72 } 73 74 @Override 75 public boolean getDetachWallpaper() { 76 return mAnimation.getDetachWallpaper(); 77 } 78 79 @Override 80 public boolean getShowWallpaper() { 81 return mAnimation.getShowWallpaper(); 82 } 83 84 @Override 85 public int getBackgroundColor() { 86 return mAnimation.getBackgroundColor(); 87 } 88 89 @Override 90 public long getDuration() { 91 return mAnimation.computeDurationHint(); 92 } 93 94 @Override 95 public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) { 96 final TmpValues tmp = mThreadLocalTmps.get(); 97 tmp.transformation.clear(); 98 mAnimation.getTransformation(currentPlayTime, tmp.transformation); 99 tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); 100 t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats); 101 t.setAlpha(leash, tmp.transformation.getAlpha()); 102 if (mStackClipMode == STACK_CLIP_NONE) { 103 t.setWindowCrop(leash, tmp.transformation.getClipRect()); 104 } else if (mStackClipMode == STACK_CLIP_AFTER_ANIM) { 105 mTmpRect.set(mStackBounds); 106 // Offset stack bounds to stack position so the final crop is in screen space. 107 mTmpRect.offsetTo(mPosition.x, mPosition.y); 108 t.setFinalCrop(leash, mTmpRect); 109 t.setWindowCrop(leash, tmp.transformation.getClipRect()); 110 } else { 111 mTmpRect.set(mStackBounds); 112 mTmpRect.intersect(tmp.transformation.getClipRect()); 113 t.setWindowCrop(leash, mTmpRect); 114 } 115 } 116 117 @Override 118 public long calculateStatusBarTransitionStartTime() { 119 TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation); 120 if (openTranslateAnimation != null) { 121 122 // Some interpolators are extremely quickly mostly finished, but not completely. For 123 // our purposes, we need to find the fraction for which ther interpolator is mostly 124 // there, and use that value for the calculation. 125 float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator()); 126 return SystemClock.uptimeMillis() 127 + openTranslateAnimation.getStartOffset() 128 + (long)(openTranslateAnimation.getDuration() * t) 129 - STATUS_BAR_TRANSITION_DURATION; 130 } else { 131 return SystemClock.uptimeMillis(); 132 } 133 } 134 135 @Override 136 public boolean canSkipFirstFrame() { 137 return mCanSkipFirstFrame; 138 } 139 140 @Override 141 public boolean needsEarlyWakeup() { 142 return mIsAppAnimation; 143 } 144 145 @Override 146 public void dump(PrintWriter pw, String prefix) { 147 pw.print(prefix); pw.println(mAnimation); 148 } 149 150 @Override 151 public void writeToProtoInner(ProtoOutputStream proto) { 152 final long token = proto.start(WINDOW); 153 proto.write(ANIMATION, mAnimation.toString()); 154 proto.end(token); 155 } 156 157 /** 158 * Tries to find a {@link TranslateAnimation} inside the {@code animation}. 159 * 160 * @return the found animation, {@code null} otherwise 161 */ 162 private static TranslateAnimation findTranslateAnimation(Animation animation) { 163 if (animation instanceof TranslateAnimation) { 164 return (TranslateAnimation) animation; 165 } else if (animation instanceof AnimationSet) { 166 AnimationSet set = (AnimationSet) animation; 167 for (int i = 0; i < set.getAnimations().size(); i++) { 168 Animation a = set.getAnimations().get(i); 169 if (a instanceof TranslateAnimation) { 170 return (TranslateAnimation) a; 171 } 172 } 173 } 174 return null; 175 } 176 177 /** 178 * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which 179 * {@code interpolator(t + eps) > 0.99}. 180 */ 181 private static float findAlmostThereFraction(Interpolator interpolator) { 182 float val = 0.5f; 183 float adj = 0.25f; 184 while (adj >= 0.01f) { 185 if (interpolator.getInterpolation(val) < 0.99f) { 186 val += adj; 187 } else { 188 val -= adj; 189 } 190 adj /= 2; 191 } 192 return val; 193 } 194 195 private static class TmpValues { 196 final Transformation transformation = new Transformation(); 197 final float[] floats = new float[9]; 198 } 199 } 200