blob: 775d5b2fb79a2634fd426eac3fce718d08645d45 [file] [log] [blame]
Evan Rosky2289ba12018-11-19 18:28:18 -08001/*
2 * Copyright (C) 2018 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.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
20import static com.android.server.wm.AnimationSpecProto.WINDOW;
21import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
22
23import android.graphics.Matrix;
24import android.graphics.Rect;
25import android.os.SystemClock;
26import android.util.proto.ProtoOutputStream;
27import android.view.DisplayInfo;
28import android.view.SurfaceControl;
29import android.view.SurfaceControl.Transaction;
30import android.view.animation.AlphaAnimation;
31import android.view.animation.Animation;
32import android.view.animation.AnimationSet;
33import android.view.animation.ClipRectAnimation;
34import android.view.animation.ScaleAnimation;
35import android.view.animation.Transformation;
36import android.view.animation.TranslateAnimation;
37
38import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
39
40import java.io.PrintWriter;
41
42/**
43 * Animation spec for changing window animations.
44 */
45public class WindowChangeAnimationSpec implements AnimationSpec {
46
47 private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
48 private final boolean mIsAppAnimation;
49 private final Rect mStartBounds;
50 private final Rect mEndBounds;
51 private final Rect mTmpRect = new Rect();
52
53 private Animation mAnimation;
54 private final boolean mIsThumbnail;
55
Evan Roskycf76bed2019-01-15 10:33:58 -080056 static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION;
57
Evan Rosky2289ba12018-11-19 18:28:18 -080058 public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
Evan Roskycf76bed2019-01-15 10:33:58 -080059 float durationScale, boolean isAppAnimation, boolean isThumbnail) {
Evan Rosky2289ba12018-11-19 18:28:18 -080060 mStartBounds = new Rect(startBounds);
61 mEndBounds = new Rect(endBounds);
62 mIsAppAnimation = isAppAnimation;
63 mIsThumbnail = isThumbnail;
Evan Roskycf76bed2019-01-15 10:33:58 -080064 createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo);
Evan Rosky2289ba12018-11-19 18:28:18 -080065 }
66
67 @Override
68 public boolean getShowWallpaper() {
69 return false;
70 }
71
72 @Override
73 public int getBackgroundColor() {
74 return 0;
75 }
76
77 @Override
78 public long getDuration() {
79 return mAnimation.getDuration();
80 }
81
82 /**
83 * This animator behaves slightly differently depending on whether the window is growing
84 * or shrinking:
85 * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
86 * snapshot.
87 * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
88 * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
89 * place.
90 * @param duration
91 * @param displayInfo
92 */
93 private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) {
94 boolean growing = mEndBounds.width() - mStartBounds.width()
95 + mEndBounds.height() - mStartBounds.height() >= 0;
96 float scalePart = 0.7f;
97 long scalePeriod = (long) (duration * scalePart);
98 float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width()
99 + (1.f - scalePart);
100 float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height()
101 + (1.f - scalePart);
102 if (mIsThumbnail) {
103 AnimationSet animSet = new AnimationSet(true);
104 Animation anim = new AlphaAnimation(1.f, 0.f);
105 anim.setDuration(scalePeriod);
106 if (!growing) {
107 anim.setStartOffset(duration - scalePeriod);
108 }
109 animSet.addAnimation(anim);
110 float endScaleX = 1.f / startScaleX;
111 float endScaleY = 1.f / startScaleY;
112 anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
113 anim.setDuration(duration);
114 animSet.addAnimation(anim);
115 mAnimation = animSet;
116 mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
117 mEndBounds.width(), mEndBounds.height());
118 } else {
119 AnimationSet animSet = new AnimationSet(true);
120 final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
121 scaleAnim.setDuration(scalePeriod);
122 if (!growing) {
123 scaleAnim.setStartOffset(duration - scalePeriod);
124 }
125 animSet.addAnimation(scaleAnim);
126 final Animation translateAnim = new TranslateAnimation(mStartBounds.left,
127 mEndBounds.left, mStartBounds.top, mEndBounds.top);
128 translateAnim.setDuration(duration);
129 animSet.addAnimation(translateAnim);
130 Rect startClip = new Rect(mStartBounds);
131 Rect endClip = new Rect(mEndBounds);
132 startClip.offsetTo(0, 0);
133 endClip.offsetTo(0, 0);
134 final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
135 clipAnim.setDuration(duration);
136 animSet.addAnimation(clipAnim);
137 mAnimation = animSet;
138 mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
139 displayInfo.appWidth, displayInfo.appHeight);
140 }
141 }
142
143 @Override
144 public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
145 final TmpValues tmp = mThreadLocalTmps.get();
146 if (mIsThumbnail) {
147 mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
148 t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats);
149 t.setAlpha(leash, tmp.mTransformation.getAlpha());
150 } else {
151 mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
152 final Matrix matrix = tmp.mTransformation.getMatrix();
153 t.setMatrix(leash, matrix, tmp.mFloats);
154
155 // The following applies an inverse scale to the clip-rect so that it crops "after" the
156 // scale instead of before.
157 tmp.mVecs[1] = tmp.mVecs[2] = 0;
158 tmp.mVecs[0] = tmp.mVecs[3] = 1;
159 matrix.mapVectors(tmp.mVecs);
160 tmp.mVecs[0] = 1.f / tmp.mVecs[0];
161 tmp.mVecs[3] = 1.f / tmp.mVecs[3];
162 final Rect clipRect = tmp.mTransformation.getClipRect();
163 mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f);
164 mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f);
165 mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f);
166 mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f);
167 t.setWindowCrop(leash, mTmpRect);
168 }
169 }
170
171 @Override
172 public long calculateStatusBarTransitionStartTime() {
173 long uptime = SystemClock.uptimeMillis();
174 return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f))
175 - STATUS_BAR_TRANSITION_DURATION);
176 }
177
178 @Override
179 public boolean canSkipFirstFrame() {
180 return false;
181 }
182
183 @Override
184 public boolean needsEarlyWakeup() {
185 return mIsAppAnimation;
186 }
187
188 @Override
189 public void dump(PrintWriter pw, String prefix) {
190 pw.print(prefix); pw.println(mAnimation.getDuration());
191 }
192
193 @Override
194 public void writeToProtoInner(ProtoOutputStream proto) {
195 final long token = proto.start(WINDOW);
196 proto.write(ANIMATION, mAnimation.toString());
197 proto.end(token);
198 }
199
200 private static class TmpValues {
201 final Transformation mTransformation = new Transformation();
202 final float[] mFloats = new float[9];
203 final float[] mVecs = new float[4];
204 }
205}