1 /* 2 * Copyright (C) 2022 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.wm.shell.activityembedding; 18 19 20 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; 21 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; 22 23 import android.content.Context; 24 import android.graphics.Rect; 25 import android.view.animation.AlphaAnimation; 26 import android.view.animation.Animation; 27 import android.view.animation.AnimationSet; 28 import android.view.animation.AnimationUtils; 29 import android.view.animation.Interpolator; 30 import android.view.animation.LinearInterpolator; 31 import android.view.animation.ScaleAnimation; 32 import android.view.animation.TranslateAnimation; 33 import android.window.TransitionInfo; 34 35 import androidx.annotation.NonNull; 36 37 import com.android.internal.policy.TransitionAnimation; 38 import com.android.wm.shell.util.TransitionUtil; 39 40 /** Animation spec for ActivityEmbedding transition. */ 41 // TODO(b/206557124): provide an easier way to customize animation 42 class ActivityEmbeddingAnimationSpec { 43 44 private static final String TAG = "ActivityEmbeddingAnimSpec"; 45 private static final int CHANGE_ANIMATION_DURATION = 517; 46 private static final int CHANGE_ANIMATION_FADE_DURATION = 80; 47 private static final int CHANGE_ANIMATION_FADE_OFFSET = 30; 48 49 private final Context mContext; 50 private final TransitionAnimation mTransitionAnimation; 51 private final Interpolator mFastOutExtraSlowInInterpolator; 52 private final LinearInterpolator mLinearInterpolator; 53 private float mTransitionAnimationScaleSetting; 54 ActivityEmbeddingAnimationSpec(@onNull Context context)55 ActivityEmbeddingAnimationSpec(@NonNull Context context) { 56 mContext = context; 57 mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG); 58 mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator( 59 mContext, android.R.interpolator.fast_out_extra_slow_in); 60 mLinearInterpolator = new LinearInterpolator(); 61 } 62 63 /** 64 * Sets transition animation scale settings value. 65 * @param scale The setting value of transition animation scale. 66 */ setAnimScaleSetting(float scale)67 void setAnimScaleSetting(float scale) { 68 mTransitionAnimationScaleSetting = scale; 69 } 70 71 /** For window that doesn't need to be animated. */ 72 @NonNull createNoopAnimation(@onNull TransitionInfo.Change change)73 static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) { 74 // Noop but just keep the window showing/hiding. 75 final float alpha = TransitionUtil.isClosingType(change.getMode()) ? 0f : 1f; 76 return new AlphaAnimation(alpha, alpha); 77 } 78 79 /** 80 * Animation that intended to show snapshot for closing animation because the closing end bounds 81 * are changed. 82 */ 83 @NonNull createShowSnapshotForClosingAnimation()84 static Animation createShowSnapshotForClosingAnimation() { 85 return new AlphaAnimation(1f, 1f); 86 } 87 88 /** Animation for window that is opening in a change transition. */ 89 @NonNull createChangeBoundsOpenAnimation(@onNull TransitionInfo.Change change, @NonNull Rect parentBounds)90 Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change, 91 @NonNull Rect parentBounds) { 92 // Use end bounds for opening. 93 final Rect bounds = change.getEndAbsBounds(); 94 final int startLeft; 95 final int startTop; 96 if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) { 97 // The window will be animated in from left or right depending on its position. 98 startTop = 0; 99 startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width(); 100 } else { 101 // The window will be animated in from top or bottom depending on its position. 102 startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height(); 103 startLeft = 0; 104 } 105 106 // The position should be 0-based as we will post translate in 107 // ActivityEmbeddingAnimationAdapter#onAnimationUpdate 108 final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0); 109 animation.setInterpolator(mFastOutExtraSlowInInterpolator); 110 animation.setDuration(CHANGE_ANIMATION_DURATION); 111 animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height()); 112 animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); 113 return animation; 114 } 115 116 /** Animation for window that is closing in a change transition. */ 117 @NonNull createChangeBoundsCloseAnimation(@onNull TransitionInfo.Change change, @NonNull Rect parentBounds)118 Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change, 119 @NonNull Rect parentBounds) { 120 // Use start bounds for closing. 121 final Rect bounds = change.getStartAbsBounds(); 122 final int endTop; 123 final int endLeft; 124 if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) { 125 // The window will be animated out to left or right depending on its position. 126 endTop = 0; 127 endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width(); 128 } else { 129 // The window will be animated out to top or bottom depending on its position. 130 endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height(); 131 endLeft = 0; 132 } 133 134 // The position should be 0-based as we will post translate in 135 // ActivityEmbeddingAnimationAdapter#onAnimationUpdate 136 final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop); 137 animation.setInterpolator(mFastOutExtraSlowInInterpolator); 138 animation.setDuration(CHANGE_ANIMATION_DURATION); 139 animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height()); 140 animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); 141 return animation; 142 } 143 144 /** 145 * Animation for window that is changing (bounds change) in a change transition. 146 * @return the return array always has two elements. The first one is for the start leash, and 147 * the second one is for the end leash. 148 */ 149 @NonNull createChangeBoundsChangeAnimations(@onNull TransitionInfo.Change change, @NonNull Rect parentBounds)150 Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change, 151 @NonNull Rect parentBounds) { 152 // Both start bounds and end bounds are in screen coordinates. We will post translate 153 // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate 154 final Rect startBounds = change.getStartAbsBounds(); 155 final Rect endBounds = change.getEndAbsBounds(); 156 float scaleX = ((float) startBounds.width()) / endBounds.width(); 157 float scaleY = ((float) startBounds.height()) / endBounds.height(); 158 // Start leash is a child of the end leash. Reverse the scale so that the start leash won't 159 // be scaled up with its parent. 160 float startScaleX = 1.f / scaleX; 161 float startScaleY = 1.f / scaleY; 162 163 // The start leash will be fade out. 164 final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */); 165 final Animation startAlpha = new AlphaAnimation(1f, 0f); 166 startAlpha.setInterpolator(mLinearInterpolator); 167 startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION); 168 startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET); 169 startSet.addAnimation(startAlpha); 170 final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY, 171 startScaleY); 172 startScale.setInterpolator(mFastOutExtraSlowInInterpolator); 173 startScale.setDuration(CHANGE_ANIMATION_DURATION); 174 startSet.addAnimation(startScale); 175 startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(), 176 endBounds.height()); 177 startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); 178 179 // The end leash will be moved into the end position while scaling. 180 final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */); 181 endSet.setInterpolator(mFastOutExtraSlowInInterpolator); 182 final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1); 183 endScale.setDuration(CHANGE_ANIMATION_DURATION); 184 endSet.addAnimation(endScale); 185 // The position should be 0-based as we will post translate in 186 // ActivityEmbeddingAnimationAdapter#onAnimationUpdate 187 final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0, 188 startBounds.top - endBounds.top, 0); 189 endTranslate.setDuration(CHANGE_ANIMATION_DURATION); 190 endSet.addAnimation(endTranslate); 191 endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(), 192 parentBounds.height()); 193 endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); 194 195 return new Animation[]{startSet, endSet}; 196 } 197 198 @NonNull loadOpenAnimation(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds)199 Animation loadOpenAnimation(@NonNull TransitionInfo info, 200 @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { 201 final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); 202 final Animation animation; 203 if (shouldShowBackdrop(info, change)) { 204 animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter 205 ? com.android.internal.R.anim.task_fragment_clear_top_open_enter 206 : com.android.internal.R.anim.task_fragment_clear_top_open_exit); 207 } else { 208 // Use the same edge extension animation as regular activity open. 209 animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter 210 ? com.android.internal.R.anim.activity_open_enter 211 : com.android.internal.R.anim.activity_open_exit); 212 } 213 // Use the whole animation bounds instead of the change bounds, so that when multiple change 214 // targets are opening at the same time, the animation applied to each will be the same. 215 // Otherwise, we may see gap between the activities that are launching together. 216 animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(), 217 wholeAnimationBounds.width(), wholeAnimationBounds.height()); 218 animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); 219 return animation; 220 } 221 222 @NonNull loadCloseAnimation(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds)223 Animation loadCloseAnimation(@NonNull TransitionInfo info, 224 @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { 225 final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); 226 final Animation animation; 227 if (shouldShowBackdrop(info, change)) { 228 animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter 229 ? com.android.internal.R.anim.task_fragment_clear_top_close_enter 230 : com.android.internal.R.anim.task_fragment_clear_top_close_exit); 231 } else { 232 // Use the same edge extension animation as regular activity close. 233 animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter 234 ? com.android.internal.R.anim.activity_close_enter 235 : com.android.internal.R.anim.activity_close_exit); 236 } 237 // Use the whole animation bounds instead of the change bounds, so that when multiple change 238 // targets are closing at the same time, the animation applied to each will be the same. 239 // Otherwise, we may see gap between the activities that are finishing together. 240 animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(), 241 wholeAnimationBounds.width(), wholeAnimationBounds.height()); 242 animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); 243 return animation; 244 } 245 shouldShowBackdrop(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change)246 private boolean shouldShowBackdrop(@NonNull TransitionInfo info, 247 @NonNull TransitionInfo.Change change) { 248 final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE, 249 mTransitionAnimation, false); 250 return a != null && a.getShowBackdrop(); 251 } 252 } 253