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