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