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