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.transition; 18 19 import static android.app.ActivityOptions.ANIM_FROM_STYLE; 20 import static android.app.ActivityOptions.ANIM_NONE; 21 import static android.view.WindowManager.TRANSIT_CLOSE; 22 import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION; 23 import static android.view.WindowManager.TRANSIT_OPEN; 24 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; 25 import static android.view.WindowManager.TRANSIT_TO_BACK; 26 import static android.view.WindowManager.TRANSIT_TO_FRONT; 27 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; 28 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 29 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 30 31 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; 32 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; 33 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; 34 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; 35 import static com.android.wm.shell.transition.Transitions.transitTypeToString; 36 37 import android.annotation.ColorInt; 38 import android.annotation.NonNull; 39 import android.annotation.Nullable; 40 import android.app.WindowConfiguration; 41 import android.graphics.Color; 42 import android.graphics.Insets; 43 import android.graphics.Rect; 44 import android.util.SparseArray; 45 import android.view.InsetsSource; 46 import android.view.InsetsState; 47 import android.view.SurfaceControl; 48 import android.view.WindowManager; 49 import android.view.animation.Animation; 50 import android.window.TransitionInfo; 51 52 import com.android.internal.R; 53 import com.android.internal.policy.TransitionAnimation; 54 import com.android.internal.protolog.ProtoLog; 55 import com.android.wm.shell.common.DisplayController; 56 import com.android.wm.shell.common.DisplayInsetsController; 57 import com.android.wm.shell.common.DisplayLayout; 58 import com.android.wm.shell.protolog.ShellProtoLogGroup; 59 import com.android.wm.shell.shared.TransitionUtil; 60 61 /** The helper class that provides methods for adding styles to transition animations. */ 62 public class TransitionAnimationHelper { 63 64 /** Loads the animation that is defined through attribute id for the given transition. */ 65 @Nullable loadAttributeAnimation(@indowManager.TransitionType int type, @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition)66 public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type, 67 @NonNull TransitionInfo info, 68 @NonNull TransitionInfo.Change change, int wallpaperTransit, 69 @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) { 70 final int changeMode = change.getMode(); 71 final int changeFlags = change.getFlags(); 72 final boolean enter = TransitionUtil.isOpeningType(changeMode); 73 final boolean isTask = change.getTaskInfo() != null; 74 final boolean isFreeform = isTask && change.getTaskInfo().isFreeform(); 75 final boolean isCoveredByOpaqueFullscreenChange = 76 isCoveredByOpaqueFullscreenChange(info, change); 77 final TransitionInfo.AnimationOptions options = change.getAnimationOptions(); 78 final int overrideType = options != null ? options.getType() : ANIM_NONE; 79 int animAttr = 0; 80 boolean translucent = false; 81 if (isDreamTransition) { 82 if (type == TRANSIT_OPEN) { 83 animAttr = enter 84 ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation 85 : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation; 86 } else if (type == TRANSIT_CLOSE) { 87 animAttr = enter 88 ? 0 89 : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation; 90 } 91 } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { 92 animAttr = enter 93 ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation 94 : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; 95 } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { 96 animAttr = enter 97 ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation 98 : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation; 99 } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) { 100 animAttr = enter 101 ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation 102 : R.styleable.WindowAnimation_wallpaperOpenExitAnimation; 103 } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) { 104 animAttr = enter 105 ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation 106 : R.styleable.WindowAnimation_wallpaperCloseExitAnimation; 107 } else if (!isCoveredByOpaqueFullscreenChange 108 && isFreeform 109 && TransitionUtil.isOpeningMode(type) 110 && change.getMode() == TRANSIT_TO_BACK) { 111 // Set translucent here so TransitionAnimation loads the appropriate animations for 112 // translucent activities and tasks later 113 translucent = (changeFlags & FLAG_TRANSLUCENT) != 0; 114 // The main Task is launching or being brought to front, this Task is being minimized 115 animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation; 116 } else if (!isCoveredByOpaqueFullscreenChange 117 && isFreeform 118 && type == TRANSIT_TO_FRONT 119 && change.getMode() == TRANSIT_TO_FRONT) { 120 // Set translucent here so TransitionAnimation loads the appropriate animations for 121 // translucent activities and tasks later 122 translucent = (changeFlags & FLAG_TRANSLUCENT) != 0; 123 // Bring the minimized Task back to front 124 animAttr = R.styleable.WindowAnimation_activityOpenEnterAnimation; 125 } else if (type == TRANSIT_OPEN) { 126 // We will translucent open animation for translucent activities and tasks. Choose 127 // WindowAnimation_activityOpenEnterAnimation and set translucent here, then 128 // TransitionAnimation loads appropriate animation later. 129 translucent = (changeFlags & FLAG_TRANSLUCENT) != 0; 130 if (isTask && translucent && !enter) { 131 // For closing translucent tasks, use the activity close animation 132 animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation; 133 } else if (isTask && !translucent) { 134 animAttr = enter 135 ? R.styleable.WindowAnimation_taskOpenEnterAnimation 136 : R.styleable.WindowAnimation_taskOpenExitAnimation; 137 } else { 138 animAttr = enter 139 ? R.styleable.WindowAnimation_activityOpenEnterAnimation 140 : R.styleable.WindowAnimation_activityOpenExitAnimation; 141 } 142 } else if (type == TRANSIT_TO_FRONT) { 143 animAttr = enter 144 ? R.styleable.WindowAnimation_taskToFrontEnterAnimation 145 : R.styleable.WindowAnimation_taskToFrontExitAnimation; 146 } else if (type == TRANSIT_CLOSE) { 147 if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { 148 translucent = true; 149 } 150 if (isTask && !translucent) { 151 animAttr = enter 152 ? R.styleable.WindowAnimation_taskCloseEnterAnimation 153 : R.styleable.WindowAnimation_taskCloseExitAnimation; 154 } else { 155 animAttr = enter 156 ? R.styleable.WindowAnimation_activityCloseEnterAnimation 157 : R.styleable.WindowAnimation_activityCloseExitAnimation; 158 } 159 } else if (type == TRANSIT_TO_BACK) { 160 animAttr = enter 161 ? R.styleable.WindowAnimation_taskToBackEnterAnimation 162 : R.styleable.WindowAnimation_taskToBackExitAnimation; 163 } 164 165 Animation a = null; 166 if (animAttr != 0) { 167 if (overrideType == ANIM_FROM_STYLE && !isTask) { 168 final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition = 169 getCustomActivityTransition(animAttr, options); 170 if (customTransition != null) { 171 a = loadCustomActivityTransition( 172 customTransition, options, enter, transitionAnimation); 173 } else { 174 a = transitionAnimation 175 .loadAnimationAttr(options.getPackageName(), options.getAnimations(), 176 animAttr, translucent); 177 } 178 } else if (translucent && !isTask && ((changeFlags & FLAGS_IS_NON_APP_WINDOW) == 0)) { 179 // Un-styled translucent activities technically have undefined animations; however, 180 // as is always the case, some apps now rely on this being no-animation, so skip 181 // loading animations here. 182 } else { 183 a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent); 184 } 185 } 186 187 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 188 "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr, 189 transitTypeToString(type), 190 enter); 191 return a; 192 } 193 getCustomActivityTransition( int animAttr, TransitionInfo.AnimationOptions options)194 static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition( 195 int animAttr, TransitionInfo.AnimationOptions options) { 196 boolean isOpen = false; 197 switch (animAttr) { 198 case R.styleable.WindowAnimation_activityOpenEnterAnimation: 199 case R.styleable.WindowAnimation_activityOpenExitAnimation: 200 isOpen = true; 201 break; 202 case R.styleable.WindowAnimation_activityCloseEnterAnimation: 203 case R.styleable.WindowAnimation_activityCloseExitAnimation: 204 break; 205 default: 206 return null; 207 } 208 209 return options.getCustomActivityTransition(isOpen); 210 } 211 212 /** 213 * Gets the final transition type from {@link TransitionInfo} for determining the animation. 214 */ getTransitionTypeFromInfo(@onNull TransitionInfo info)215 public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) { 216 final int type = info.getType(); 217 // This back navigation is canceled, check whether the transition should be open or close 218 if (type == TRANSIT_PREPARE_BACK_NAVIGATION 219 || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) { 220 if (!info.getChanges().isEmpty()) { 221 final TransitionInfo.Change change = info.getChanges().get(0); 222 return TransitionUtil.isOpeningMode(change.getMode()) 223 ? TRANSIT_OPEN : TRANSIT_CLOSE; 224 } 225 } 226 // If the info transition type is opening transition, iterate its changes to see if it 227 // has any opening change, if none, returns TRANSIT_CLOSE type for closing animation. 228 if (type == TRANSIT_OPEN) { 229 boolean hasOpenTransit = false; 230 for (TransitionInfo.Change change : info.getChanges()) { 231 if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY)) 232 && !TransitionUtil.isOrderOnly(change)) { 233 // This isn't an activity-level transition. 234 return type; 235 } 236 if (change.getTaskInfo() != null 237 && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) { 238 // Ignore non-activity containers. 239 continue; 240 } 241 if (change.getMode() == TRANSIT_OPEN) { 242 hasOpenTransit = true; 243 break; 244 } 245 } 246 if (!hasOpenTransit) { 247 return TRANSIT_CLOSE; 248 } 249 } 250 return type; 251 } 252 loadCustomActivityTransition( @onNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim, TransitionInfo.AnimationOptions options, boolean enter, TransitionAnimation transitionAnimation)253 static Animation loadCustomActivityTransition( 254 @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim, 255 TransitionInfo.AnimationOptions options, boolean enter, 256 TransitionAnimation transitionAnimation) { 257 final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(), 258 enter ? transitionAnim.getCustomEnterResId() 259 : transitionAnim.getCustomExitResId()); 260 if (a != null && transitionAnim.getCustomBackgroundColor() != 0) { 261 a.setBackdropColor(transitionAnim.getCustomBackgroundColor()); 262 } 263 return a; 264 } 265 266 /** 267 * Gets the background {@link ColorInt} for the given transition animation if it is set. 268 * 269 * @param defaultColor {@link ColorInt} to return if there is no background color specified by 270 * the given transition animation. 271 */ 272 @ColorInt getTransitionBackgroundColorIfSet(@onNull TransitionInfo.Change change, @NonNull Animation a, @ColorInt int defaultColor)273 public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo.Change change, 274 @NonNull Animation a, @ColorInt int defaultColor) { 275 if (!a.getShowBackdrop()) { 276 return defaultColor; 277 } else if (a.getBackdropColor() != 0) { 278 // Otherwise fallback on the background color provided through the animation 279 // definition. 280 return a.getBackdropColor(); 281 } else if (change.getBackgroundColor() != 0) { 282 // Otherwise default to the window's background color if provided through 283 // the theme as the background color for the animation - the top most window 284 // with a valid background color and showBackground set takes precedence. 285 return change.getBackgroundColor(); 286 } 287 return defaultColor; 288 } 289 290 /** 291 * Adds the given {@code backgroundColor} as the background color to the transition animation. 292 */ addBackgroundToTransition(@onNull SurfaceControl rootLeash, @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)293 public static void addBackgroundToTransition(@NonNull SurfaceControl rootLeash, 294 @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction, 295 @NonNull SurfaceControl.Transaction finishTransaction) { 296 if (backgroundColor == 0) { 297 // No background color. 298 return; 299 } 300 final Color bgColor = Color.valueOf(backgroundColor); 301 final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() }; 302 final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder() 303 .setName("Animation Background") 304 .setParent(rootLeash) 305 .setColorLayer() 306 .setOpaque(true) 307 .setCallsite("TransitionAnimationHelper.addBackgroundToTransition") 308 .build(); 309 startTransaction 310 .setLayer(animationBackgroundSurface, Integer.MIN_VALUE) 311 .setColor(animationBackgroundSurface, colorArray) 312 .show(animationBackgroundSurface); 313 finishTransaction.remove(animationBackgroundSurface); 314 } 315 316 /** 317 * Returns whether there is an opaque fullscreen Change positioned in front of the given Change 318 * in the given TransitionInfo. 319 */ isCoveredByOpaqueFullscreenChange( TransitionInfo info, TransitionInfo.Change change)320 static boolean isCoveredByOpaqueFullscreenChange( 321 TransitionInfo info, TransitionInfo.Change change) { 322 // TransitionInfo#getChanges() are ordered from front to back 323 for (TransitionInfo.Change coveringChange : info.getChanges()) { 324 if (coveringChange == change) { 325 return false; 326 } 327 if ((coveringChange.getFlags() & FLAG_TRANSLUCENT) == 0 328 && coveringChange.getTaskInfo() != null 329 && coveringChange.getTaskInfo().getWindowingMode() 330 == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) { 331 return true; 332 } 333 } 334 return false; 335 } 336 337 /** 338 * In some situations (eg. TaskBar) the content area of a display appears to be rounded. For 339 * these situations, we may want the animation to also express the same rounded corners (even 340 * though in steady-state, the app internally manages the insets). This class Keeps track of, 341 * and provides, the bounds of rounded-corner display content. 342 * 343 * This is used to enable already-running animations to adapt to changes in taskbar/navbar 344 * position live. 345 */ 346 public static class RoundedContentPerDisplay implements 347 DisplayInsetsController.OnInsetsChangedListener { 348 349 /** The current bounds of the display content (post-inset). */ 350 final Rect mBounds = new Rect(); 351 352 @Override insetsChanged(InsetsState insetsState)353 public void insetsChanged(InsetsState insetsState) { 354 Insets insets = Insets.NONE; 355 for (int i = insetsState.sourceSize() - 1; i >= 0; i--) { 356 final InsetsSource source = insetsState.sourceAt(i); 357 if (!source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { 358 continue; 359 } 360 insets = Insets.max(source.calculateInsets(insetsState.getDisplayFrame(), false), 361 insets); 362 } 363 mBounds.set(insetsState.getDisplayFrame()); 364 mBounds.inset(insets); 365 } 366 } 367 368 /** 369 * Keeps track of the bounds of rounded-corner display content (post-inset). 370 * 371 * @see RoundedContentPerDisplay 372 */ 373 public static class RoundedContentTracker implements 374 DisplayController.OnDisplaysChangedListener { 375 final DisplayController mDisplayController; 376 final DisplayInsetsController mDisplayInsetsController; 377 final SparseArray<RoundedContentPerDisplay> mPerDisplay = new SparseArray<>(); 378 RoundedContentTracker(DisplayController dc, DisplayInsetsController dic)379 RoundedContentTracker(DisplayController dc, DisplayInsetsController dic) { 380 mDisplayController = dc; 381 mDisplayInsetsController = dic; 382 } 383 init()384 void init() { 385 mDisplayController.addDisplayWindowListener(this); 386 } 387 forDisplay(int displayId)388 RoundedContentPerDisplay forDisplay(int displayId) { 389 return mPerDisplay.get(displayId); 390 } 391 392 @Override onDisplayAdded(int displayId)393 public void onDisplayAdded(int displayId) { 394 final RoundedContentPerDisplay perDisplay = new RoundedContentPerDisplay(); 395 mDisplayInsetsController.addInsetsChangedListener(displayId, perDisplay); 396 mPerDisplay.put(displayId, perDisplay); 397 final DisplayLayout dl = mDisplayController.getDisplayLayout(displayId); 398 perDisplay.mBounds.set(0, 0, dl.width(), dl.height()); 399 } 400 401 @Override onDisplayRemoved(int displayId)402 public void onDisplayRemoved(int displayId) { 403 final RoundedContentPerDisplay listener = mPerDisplay.removeReturnOld(displayId); 404 if (listener != null) { 405 mDisplayInsetsController.removeInsetsChangedListener(displayId, listener); 406 } 407 } 408 } 409 } 410