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_OPEN; 23 import static android.view.WindowManager.TRANSIT_TO_BACK; 24 import static android.view.WindowManager.TRANSIT_TO_FRONT; 25 import static android.view.WindowManager.transitTypeToString; 26 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; 27 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 28 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 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 36 import android.annotation.ColorInt; 37 import android.annotation.NonNull; 38 import android.annotation.Nullable; 39 import android.graphics.BitmapShader; 40 import android.graphics.Canvas; 41 import android.graphics.Color; 42 import android.graphics.Insets; 43 import android.graphics.Paint; 44 import android.graphics.PixelFormat; 45 import android.graphics.Rect; 46 import android.graphics.Shader; 47 import android.view.Surface; 48 import android.view.SurfaceControl; 49 import android.view.WindowManager; 50 import android.view.animation.Animation; 51 import android.view.animation.Transformation; 52 import android.window.ScreenCapture; 53 import android.window.TransitionInfo; 54 55 import com.android.internal.R; 56 import com.android.internal.policy.TransitionAnimation; 57 import com.android.internal.protolog.common.ProtoLog; 58 import com.android.window.flags.Flags; 59 import com.android.wm.shell.protolog.ShellProtoLogGroup; 60 import com.android.wm.shell.shared.TransitionUtil; 61 62 /** The helper class that provides methods for adding styles to transition animations. */ 63 public class TransitionAnimationHelper { 64 65 /** Loads the animation that is defined through attribute id for the given transition. */ 66 @Nullable loadAttributeAnimation(@indowManager.TransitionType int type, @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition)67 public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type, 68 @NonNull TransitionInfo info, 69 @NonNull TransitionInfo.Change change, int wallpaperTransit, 70 @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) { 71 final int changeMode = change.getMode(); 72 final int changeFlags = change.getFlags(); 73 final boolean enter = TransitionUtil.isOpeningType(changeMode); 74 final boolean isTask = change.getTaskInfo() != null; 75 final TransitionInfo.AnimationOptions options; 76 if (Flags.moveAnimationOptionsToChange()) { 77 options = change.getAnimationOptions(); 78 } else { 79 options = info.getAnimationOptions(); 80 } 81 final int overrideType = options != null ? options.getType() : ANIM_NONE; 82 int animAttr = 0; 83 boolean translucent = false; 84 if (isDreamTransition) { 85 if (type == TRANSIT_OPEN) { 86 animAttr = enter 87 ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation 88 : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation; 89 } else if (type == TRANSIT_CLOSE) { 90 animAttr = enter 91 ? 0 92 : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation; 93 } 94 } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { 95 animAttr = enter 96 ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation 97 : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; 98 } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { 99 animAttr = enter 100 ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation 101 : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation; 102 } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) { 103 animAttr = enter 104 ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation 105 : R.styleable.WindowAnimation_wallpaperOpenExitAnimation; 106 } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) { 107 animAttr = enter 108 ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation 109 : R.styleable.WindowAnimation_wallpaperCloseExitAnimation; 110 } else if (type == TRANSIT_OPEN) { 111 // We will translucent open animation for translucent activities and tasks. Choose 112 // WindowAnimation_activityOpenEnterAnimation and set translucent here, then 113 // TransitionAnimation loads appropriate animation later. 114 translucent = (changeFlags & FLAG_TRANSLUCENT) != 0; 115 if (isTask && translucent && !enter) { 116 // For closing translucent tasks, use the activity close animation 117 animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation; 118 } else if (isTask && !translucent) { 119 animAttr = enter 120 ? R.styleable.WindowAnimation_taskOpenEnterAnimation 121 : R.styleable.WindowAnimation_taskOpenExitAnimation; 122 } else { 123 animAttr = enter 124 ? R.styleable.WindowAnimation_activityOpenEnterAnimation 125 : R.styleable.WindowAnimation_activityOpenExitAnimation; 126 } 127 } else if (type == TRANSIT_TO_FRONT) { 128 animAttr = enter 129 ? R.styleable.WindowAnimation_taskToFrontEnterAnimation 130 : R.styleable.WindowAnimation_taskToFrontExitAnimation; 131 } else if (type == TRANSIT_CLOSE) { 132 if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { 133 translucent = true; 134 } 135 if (isTask && !translucent) { 136 animAttr = enter 137 ? R.styleable.WindowAnimation_taskCloseEnterAnimation 138 : R.styleable.WindowAnimation_taskCloseExitAnimation; 139 } else { 140 animAttr = enter 141 ? R.styleable.WindowAnimation_activityCloseEnterAnimation 142 : R.styleable.WindowAnimation_activityCloseExitAnimation; 143 } 144 } else if (type == TRANSIT_TO_BACK) { 145 animAttr = enter 146 ? R.styleable.WindowAnimation_taskToBackEnterAnimation 147 : R.styleable.WindowAnimation_taskToBackExitAnimation; 148 } 149 150 Animation a = null; 151 if (animAttr != 0) { 152 if (overrideType == ANIM_FROM_STYLE && !isTask) { 153 final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition = 154 getCustomActivityTransition(animAttr, options); 155 if (customTransition != null) { 156 a = loadCustomActivityTransition( 157 customTransition, options, enter, transitionAnimation); 158 } else { 159 a = transitionAnimation 160 .loadAnimationAttr(options.getPackageName(), options.getAnimations(), 161 animAttr, translucent); 162 } 163 } else if (translucent && !isTask && ((changeFlags & FLAGS_IS_NON_APP_WINDOW) == 0)) { 164 // Un-styled translucent activities technically have undefined animations; however, 165 // as is always the case, some apps now rely on this being no-animation, so skip 166 // loading animations here. 167 } else { 168 a = transitionAnimation.loadDefaultAnimationAttr(animAttr, translucent); 169 } 170 } 171 172 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 173 "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr, 174 transitTypeToString(type), 175 enter); 176 return a; 177 } 178 getCustomActivityTransition( int animAttr, TransitionInfo.AnimationOptions options)179 static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition( 180 int animAttr, TransitionInfo.AnimationOptions options) { 181 boolean isOpen = false; 182 switch (animAttr) { 183 case R.styleable.WindowAnimation_activityOpenEnterAnimation: 184 case R.styleable.WindowAnimation_activityOpenExitAnimation: 185 isOpen = true; 186 break; 187 case R.styleable.WindowAnimation_activityCloseEnterAnimation: 188 case R.styleable.WindowAnimation_activityCloseExitAnimation: 189 break; 190 default: 191 return null; 192 } 193 194 return options.getCustomActivityTransition(isOpen); 195 } 196 197 /** 198 * Gets the final transition type from {@link TransitionInfo} for determining the animation. 199 */ getTransitionTypeFromInfo(@onNull TransitionInfo info)200 public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) { 201 final int type = info.getType(); 202 // If the info transition type is opening transition, iterate its changes to see if it 203 // has any opening change, if none, returns TRANSIT_CLOSE type for closing animation. 204 if (type == TRANSIT_OPEN) { 205 boolean hasOpenTransit = false; 206 for (TransitionInfo.Change change : info.getChanges()) { 207 if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY)) 208 && !TransitionUtil.isOrderOnly(change)) { 209 // This isn't an activity-level transition. 210 return type; 211 } 212 if (change.getTaskInfo() != null 213 && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) { 214 // Ignore non-activity containers. 215 continue; 216 } 217 if (change.getMode() == TRANSIT_OPEN) { 218 hasOpenTransit = true; 219 break; 220 } 221 } 222 if (!hasOpenTransit) { 223 return TRANSIT_CLOSE; 224 } 225 } 226 return type; 227 } 228 loadCustomActivityTransition( @onNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim, TransitionInfo.AnimationOptions options, boolean enter, TransitionAnimation transitionAnimation)229 static Animation loadCustomActivityTransition( 230 @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim, 231 TransitionInfo.AnimationOptions options, boolean enter, 232 TransitionAnimation transitionAnimation) { 233 final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(), 234 enter ? transitionAnim.getCustomEnterResId() 235 : transitionAnim.getCustomExitResId()); 236 if (a != null && transitionAnim.getCustomBackgroundColor() != 0) { 237 a.setBackdropColor(transitionAnim.getCustomBackgroundColor()); 238 } 239 return a; 240 } 241 242 /** 243 * Gets the background {@link ColorInt} for the given transition animation if it is set. 244 * 245 * @param defaultColor {@link ColorInt} to return if there is no background color specified by 246 * the given transition animation. 247 */ 248 @ColorInt getTransitionBackgroundColorIfSet(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Animation a, @ColorInt int defaultColor)249 public static int getTransitionBackgroundColorIfSet(@NonNull TransitionInfo info, 250 @NonNull TransitionInfo.Change change, @NonNull Animation a, 251 @ColorInt int defaultColor) { 252 if (!a.getShowBackdrop()) { 253 return defaultColor; 254 } 255 if (!Flags.moveAnimationOptionsToChange() && info.getAnimationOptions() != null 256 && info.getAnimationOptions().getBackgroundColor() != 0) { 257 // If available use the background color provided through AnimationOptions 258 return info.getAnimationOptions().getBackgroundColor(); 259 } else if (a.getBackdropColor() != 0) { 260 // Otherwise fallback on the background color provided through the animation 261 // definition. 262 return a.getBackdropColor(); 263 } else if (change.getBackgroundColor() != 0) { 264 // Otherwise default to the window's background color if provided through 265 // the theme as the background color for the animation - the top most window 266 // with a valid background color and showBackground set takes precedence. 267 return change.getBackgroundColor(); 268 } 269 return defaultColor; 270 } 271 272 /** 273 * Adds the given {@code backgroundColor} as the background color to the transition animation. 274 */ addBackgroundToTransition(@onNull SurfaceControl rootLeash, @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)275 public static void addBackgroundToTransition(@NonNull SurfaceControl rootLeash, 276 @ColorInt int backgroundColor, @NonNull SurfaceControl.Transaction startTransaction, 277 @NonNull SurfaceControl.Transaction finishTransaction) { 278 if (backgroundColor == 0) { 279 // No background color. 280 return; 281 } 282 final Color bgColor = Color.valueOf(backgroundColor); 283 final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() }; 284 final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder() 285 .setName("Animation Background") 286 .setParent(rootLeash) 287 .setColorLayer() 288 .setOpaque(true) 289 .setCallsite("TransitionAnimationHelper.addBackgroundToTransition") 290 .build(); 291 startTransaction 292 .setLayer(animationBackgroundSurface, Integer.MIN_VALUE) 293 .setColor(animationBackgroundSurface, colorArray) 294 .show(animationBackgroundSurface); 295 finishTransaction.remove(animationBackgroundSurface); 296 } 297 298 /** 299 * Adds edge extension surface to the given {@code change} for edge extension animation. 300 */ edgeExtendWindow(@onNull TransitionInfo.Change change, @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)301 public static void edgeExtendWindow(@NonNull TransitionInfo.Change change, 302 @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction, 303 @NonNull SurfaceControl.Transaction finishTransaction) { 304 // Do not create edge extension surface for transfer starting window change. 305 // The app surface could be empty thus nothing can draw on the hardware renderer, which will 306 // block this thread when calling Surface#unlockCanvasAndPost. 307 if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { 308 return; 309 } 310 final Transformation transformationAtStart = new Transformation(); 311 a.getTransformationAt(0, transformationAtStart); 312 final Transformation transformationAtEnd = new Transformation(); 313 a.getTransformationAt(1, transformationAtEnd); 314 315 // We want to create an extension surface that is the maximal size and the animation will 316 // take care of cropping any part that overflows. 317 final Insets maxExtensionInsets = Insets.min( 318 transformationAtStart.getInsets(), transformationAtEnd.getInsets()); 319 320 final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(), 321 change.getEndAbsBounds().height()); 322 final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(), 323 change.getEndAbsBounds().width()); 324 if (maxExtensionInsets.left < 0) { 325 final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); 326 final Rect extensionRect = new Rect(0, 0, 327 -maxExtensionInsets.left, targetSurfaceHeight); 328 final int xPos = maxExtensionInsets.left; 329 final int yPos = 0; 330 createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, 331 "Left Edge Extension", startTransaction, finishTransaction); 332 } 333 334 if (maxExtensionInsets.top < 0) { 335 final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); 336 final Rect extensionRect = new Rect(0, 0, 337 targetSurfaceWidth, -maxExtensionInsets.top); 338 final int xPos = 0; 339 final int yPos = maxExtensionInsets.top; 340 createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, 341 "Top Edge Extension", startTransaction, finishTransaction); 342 } 343 344 if (maxExtensionInsets.right < 0) { 345 final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, 346 targetSurfaceWidth, targetSurfaceHeight); 347 final Rect extensionRect = new Rect(0, 0, 348 -maxExtensionInsets.right, targetSurfaceHeight); 349 final int xPos = targetSurfaceWidth; 350 final int yPos = 0; 351 createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, 352 "Right Edge Extension", startTransaction, finishTransaction); 353 } 354 355 if (maxExtensionInsets.bottom < 0) { 356 final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, 357 targetSurfaceWidth, targetSurfaceHeight); 358 final Rect extensionRect = new Rect(0, 0, 359 targetSurfaceWidth, -maxExtensionInsets.bottom); 360 final int xPos = maxExtensionInsets.left; 361 final int yPos = targetSurfaceHeight; 362 createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, 363 "Bottom Edge Extension", startTransaction, finishTransaction); 364 } 365 } 366 367 /** 368 * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension 369 * animation. 370 */ createExtensionSurface(@onNull SurfaceControl surfaceToExtend, @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos, @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)371 private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend, 372 @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos, 373 @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction, 374 @NonNull SurfaceControl.Transaction finishTransaction) { 375 final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() 376 .setName(layerName) 377 .setParent(surfaceToExtend) 378 .setHidden(true) 379 .setCallsite("TransitionAnimationHelper#createExtensionSurface") 380 .setOpaque(true) 381 .setBufferSize(extensionRect.width(), extensionRect.height()) 382 .build(); 383 384 final ScreenCapture.LayerCaptureArgs captureArgs = 385 new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend) 386 .setSourceCrop(edgeBounds) 387 .setFrameScale(1) 388 .setPixelFormat(PixelFormat.RGBA_8888) 389 .setChildrenOnly(true) 390 .setAllowProtected(false) 391 .setCaptureSecureLayers(true) 392 .build(); 393 final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer = 394 ScreenCapture.captureLayers(captureArgs); 395 396 if (edgeBuffer == null) { 397 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 398 "Failed to capture edge of window."); 399 return null; 400 } 401 402 final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(), 403 Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 404 final Paint paint = new Paint(); 405 paint.setShader(shader); 406 407 final Surface surface = new Surface(edgeExtensionLayer); 408 final Canvas c = surface.lockHardwareCanvas(); 409 c.drawRect(extensionRect, paint); 410 surface.unlockCanvasAndPost(c); 411 surface.release(); 412 413 startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); 414 startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); 415 startTransaction.setVisibility(edgeExtensionLayer, true); 416 finishTransaction.remove(edgeExtensionLayer); 417 418 return edgeExtensionLayer; 419 } 420 } 421