1 /* 2 * Copyright (C) 2021 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_CLIP_REVEAL; 20 import static android.app.ActivityOptions.ANIM_CUSTOM; 21 import static android.app.ActivityOptions.ANIM_NONE; 22 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; 23 import static android.app.ActivityOptions.ANIM_SCALE_UP; 24 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; 25 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; 26 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; 27 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; 28 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 29 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; 30 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE; 31 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE; 32 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; 33 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; 34 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; 35 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; 36 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; 37 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; 38 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; 39 import static android.view.WindowManager.TRANSIT_CHANGE; 40 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; 41 import static android.view.WindowManager.TRANSIT_RELAUNCH; 42 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; 43 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; 44 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; 45 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; 46 import static android.window.TransitionInfo.FLAG_FILLS_TASK; 47 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 48 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; 49 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 50 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; 51 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 52 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; 53 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 54 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 55 56 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; 57 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; 58 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; 59 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; 60 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; 61 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; 62 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; 63 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; 64 65 import android.animation.Animator; 66 import android.animation.AnimatorListenerAdapter; 67 import android.animation.ValueAnimator; 68 import android.annotation.ColorInt; 69 import android.annotation.NonNull; 70 import android.annotation.Nullable; 71 import android.app.ActivityManager; 72 import android.app.ActivityThread; 73 import android.app.admin.DevicePolicyManager; 74 import android.content.BroadcastReceiver; 75 import android.content.Context; 76 import android.content.Intent; 77 import android.content.IntentFilter; 78 import android.graphics.Color; 79 import android.graphics.Insets; 80 import android.graphics.Point; 81 import android.graphics.Rect; 82 import android.graphics.drawable.Drawable; 83 import android.hardware.HardwareBuffer; 84 import android.os.Handler; 85 import android.os.IBinder; 86 import android.os.UserHandle; 87 import android.util.ArrayMap; 88 import android.view.Choreographer; 89 import android.view.SurfaceControl; 90 import android.view.SurfaceSession; 91 import android.view.WindowManager; 92 import android.view.animation.AlphaAnimation; 93 import android.view.animation.Animation; 94 import android.view.animation.Transformation; 95 import android.window.TransitionInfo; 96 import android.window.TransitionMetrics; 97 import android.window.TransitionRequestInfo; 98 import android.window.WindowContainerTransaction; 99 100 import com.android.internal.R; 101 import com.android.internal.annotations.VisibleForTesting; 102 import com.android.internal.policy.ScreenDecorationsUtils; 103 import com.android.internal.policy.TransitionAnimation; 104 import com.android.internal.protolog.common.ProtoLog; 105 import com.android.wm.shell.RootTaskDisplayAreaOrganizer; 106 import com.android.wm.shell.common.DisplayController; 107 import com.android.wm.shell.common.DisplayLayout; 108 import com.android.wm.shell.common.ShellExecutor; 109 import com.android.wm.shell.common.TransactionPool; 110 import com.android.wm.shell.protolog.ShellProtoLogGroup; 111 import com.android.wm.shell.sysui.ShellInit; 112 import com.android.wm.shell.util.TransitionUtil; 113 114 import java.util.ArrayList; 115 import java.util.List; 116 import java.util.function.Consumer; 117 118 /** The default handler that handles anything not already handled. */ 119 public class DefaultTransitionHandler implements Transitions.TransitionHandler { 120 private static final int MAX_ANIMATION_DURATION = 3000; 121 122 private final TransactionPool mTransactionPool; 123 private final DisplayController mDisplayController; 124 private final Context mContext; 125 private final Handler mMainHandler; 126 private final ShellExecutor mMainExecutor; 127 private final ShellExecutor mAnimExecutor; 128 private final TransitionAnimation mTransitionAnimation; 129 private final DevicePolicyManager mDevicePolicyManager; 130 131 private final SurfaceSession mSurfaceSession = new SurfaceSession(); 132 133 /** Keeps track of the currently-running animations associated with each transition. */ 134 private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); 135 136 private final CounterRotatorHelper mRotator = new CounterRotatorHelper(); 137 private final Rect mInsets = new Rect(0, 0, 0, 0); 138 private float mTransitionAnimationScaleSetting = 1.0f; 139 140 private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; 141 private final int mCurrentUserId; 142 143 private Drawable mEnterpriseThumbnailDrawable; 144 145 private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() { 146 @Override 147 public void onReceive(Context context, Intent intent) { 148 if (intent.getIntExtra(EXTRA_RESOURCE_TYPE, /* default= */ -1) 149 != EXTRA_RESOURCE_TYPE_DRAWABLE) { 150 return; 151 } 152 updateEnterpriseThumbnailDrawable(); 153 } 154 }; 155 DefaultTransitionHandler(@onNull Context context, @NonNull ShellInit shellInit, @NonNull DisplayController displayController, @NonNull TransactionPool transactionPool, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer)156 DefaultTransitionHandler(@NonNull Context context, 157 @NonNull ShellInit shellInit, 158 @NonNull DisplayController displayController, 159 @NonNull TransactionPool transactionPool, 160 @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, 161 @NonNull ShellExecutor animExecutor, 162 @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) { 163 mDisplayController = displayController; 164 mTransactionPool = transactionPool; 165 mContext = context; 166 mMainHandler = mainHandler; 167 mMainExecutor = mainExecutor; 168 mAnimExecutor = animExecutor; 169 mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG); 170 mCurrentUserId = UserHandle.myUserId(); 171 mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class); 172 shellInit.addInitCallback(this::onInit, this); 173 mRootTDAOrganizer = rootTDAOrganizer; 174 } 175 onInit()176 private void onInit() { 177 updateEnterpriseThumbnailDrawable(); 178 mContext.registerReceiver( 179 mEnterpriseResourceUpdatedReceiver, 180 new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED), 181 /* broadcastPermission = */ null, 182 mMainHandler); 183 184 TransitionAnimation.initAttributeCache(mContext, mMainHandler); 185 } 186 updateEnterpriseThumbnailDrawable()187 private void updateEnterpriseThumbnailDrawable() { 188 mEnterpriseThumbnailDrawable = mDevicePolicyManager.getResources().getDrawable( 189 WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION, 190 () -> mContext.getDrawable(R.drawable.ic_corp_badge)); 191 } 192 193 @VisibleForTesting getRotationAnimationHint(@onNull TransitionInfo.Change displayChange, @NonNull TransitionInfo info, @NonNull DisplayController displayController)194 static int getRotationAnimationHint(@NonNull TransitionInfo.Change displayChange, 195 @NonNull TransitionInfo info, @NonNull DisplayController displayController) { 196 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 197 "Display is changing, resolve the animation hint."); 198 // The explicit request of display has the highest priority. 199 if (displayChange.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS) { 200 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 201 " display requests explicit seamless"); 202 return ROTATION_ANIMATION_SEAMLESS; 203 } 204 205 boolean allTasksSeamless = false; 206 boolean rejectSeamless = false; 207 ActivityManager.RunningTaskInfo topTaskInfo = null; 208 int animationHint = ROTATION_ANIMATION_ROTATE; 209 // Traverse in top-to-bottom order so that the first task is top-most. 210 final int size = info.getChanges().size(); 211 for (int i = 0; i < size; ++i) { 212 final TransitionInfo.Change change = info.getChanges().get(i); 213 214 // Only look at changing things. showing/hiding don't need to rotate. 215 if (change.getMode() != TRANSIT_CHANGE) continue; 216 217 // This container isn't rotating, so we can ignore it. 218 if (change.getEndRotation() == change.getStartRotation()) continue; 219 if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) { 220 // In the presence of System Alert windows we can not seamlessly rotate. 221 if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { 222 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 223 " display has system alert windows, so not seamless."); 224 rejectSeamless = true; 225 } 226 } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { 227 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) { 228 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 229 " wallpaper is participating but isn't seamless."); 230 rejectSeamless = true; 231 } 232 } else if (change.getTaskInfo() != null) { 233 final int anim = change.getRotationAnimation(); 234 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 235 final boolean isTopTask = topTaskInfo == null; 236 if (isTopTask) { 237 topTaskInfo = taskInfo; 238 if (anim != ROTATION_ANIMATION_UNSPECIFIED 239 && anim != ROTATION_ANIMATION_SEAMLESS) { 240 animationHint = anim; 241 } 242 } 243 // We only enable seamless rotation if all the visible task windows requested it. 244 if (anim != ROTATION_ANIMATION_SEAMLESS) { 245 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 246 " task %s isn't requesting seamless, so not seamless.", 247 taskInfo.taskId); 248 allTasksSeamless = false; 249 } else if (isTopTask) { 250 allTasksSeamless = true; 251 } 252 } 253 } 254 255 if (!allTasksSeamless || rejectSeamless) { 256 return animationHint; 257 } 258 259 // This is the only way to get display-id currently, so check display capabilities here. 260 final DisplayLayout displayLayout = displayController.getDisplayLayout( 261 topTaskInfo.displayId); 262 // This condition should be true when using gesture navigation or the screen size is large 263 // (>600dp) because the bar is small relative to screen. 264 if (displayLayout.allowSeamlessRotationDespiteNavBarMoving()) { 265 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " nav bar allows seamless."); 266 return ROTATION_ANIMATION_SEAMLESS; 267 } 268 // For the upside down rotation we don't rotate seamlessly as the navigation bar moves 269 // position. Note most apps (using orientation:sensor or user as opposed to fullSensor) 270 // will not enter the reverse portrait orientation, so actually the orientation won't 271 // change at all. 272 final int upsideDownRotation = displayLayout.getUpsideDownRotation(); 273 if (displayChange.getStartRotation() == upsideDownRotation 274 || displayChange.getEndRotation() == upsideDownRotation) { 275 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 276 " rotation involves upside-down portrait, so not seamless."); 277 return animationHint; 278 } 279 280 // If the navigation bar cannot change sides, then it will jump when changing orientation 281 // so do not use seamless rotation. 282 if (!displayLayout.navigationBarCanMove()) { 283 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 284 " nav bar changes sides, so not seamless."); 285 return animationHint; 286 } 287 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless."); 288 return ROTATION_ANIMATION_SEAMLESS; 289 } 290 291 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)292 public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 293 @NonNull SurfaceControl.Transaction startTransaction, 294 @NonNull SurfaceControl.Transaction finishTransaction, 295 @NonNull Transitions.TransitionFinishCallback finishCallback) { 296 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 297 "start default transition animation, info = %s", info); 298 // If keyguard goes away, we should loadKeyguardExitAnimation. Otherwise this just 299 // immediately finishes since there is no animation for screen-wake. 300 if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) { 301 startTransaction.apply(); 302 finishCallback.onTransitionFinished(null /* wct */); 303 return true; 304 } 305 306 // Early check if the transition doesn't warrant an animation. 307 if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info) 308 || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) { 309 startTransaction.apply(); 310 finishTransaction.apply(); 311 finishCallback.onTransitionFinished(null /* wct */); 312 return true; 313 } 314 315 if (mAnimations.containsKey(transition)) { 316 throw new IllegalStateException("Got a duplicate startAnimation call for " 317 + transition); 318 } 319 final ArrayList<Animator> animations = new ArrayList<>(); 320 mAnimations.put(transition, animations); 321 322 final Runnable onAnimFinish = () -> { 323 if (!animations.isEmpty()) return; 324 mAnimations.remove(transition); 325 finishCallback.onTransitionFinished(null /* wct */); 326 }; 327 328 final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = 329 new ArrayList<>(); 330 331 @ColorInt int backgroundColorForTransition = 0; 332 final int wallpaperTransit = getWallpaperTransitType(info); 333 boolean isDisplayRotationAnimationStarted = false; 334 final boolean isDreamTransition = isDreamTransition(info); 335 final boolean isOnlyTranslucent = isOnlyTranslucent(info); 336 337 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 338 final TransitionInfo.Change change = info.getChanges().get(i); 339 if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY 340 | FLAG_IS_BEHIND_STARTING_WINDOW)) { 341 // Don't animate embedded activity if it is covered by the starting window. 342 // Non-embedded case still needs animation because the container can still animate 343 // the starting window together, e.g. CLOSE or CHANGE type. 344 continue; 345 } 346 if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) { 347 // Wallpaper, IME, and system windows don't need any default animations. 348 continue; 349 } 350 final boolean isTask = change.getTaskInfo() != null; 351 final int mode = change.getMode(); 352 boolean isSeamlessDisplayChange = false; 353 354 if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { 355 if (info.getType() == TRANSIT_CHANGE) { 356 final int anim = getRotationAnimationHint(change, info, mDisplayController); 357 isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS; 358 if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) { 359 startRotationAnimation(startTransaction, change, info, anim, animations, 360 onAnimFinish); 361 isDisplayRotationAnimationStarted = true; 362 continue; 363 } 364 } else { 365 // Opening/closing an app into a new orientation. 366 mRotator.handleClosingChanges(info, startTransaction, change); 367 } 368 } 369 370 if (mode == TRANSIT_CHANGE) { 371 // If task is child task, only set position in parent and update crop when needed. 372 if (isTask && change.getParent() != null 373 && info.getChange(change.getParent()).getTaskInfo() != null) { 374 final Point positionInParent = change.getTaskInfo().positionInParent; 375 startTransaction.setPosition(change.getLeash(), 376 positionInParent.x, positionInParent.y); 377 378 if (!change.getEndAbsBounds().equals( 379 info.getChange(change.getParent()).getEndAbsBounds())) { 380 startTransaction.setWindowCrop(change.getLeash(), 381 change.getEndAbsBounds().width(), 382 change.getEndAbsBounds().height()); 383 } 384 385 continue; 386 } 387 388 // There is no default animation for Pip window in rotation transition, and the 389 // PipTransition will update the surface of its own window at start/finish. 390 if (isTask && change.getTaskInfo().configuration.windowConfiguration 391 .getWindowingMode() == WINDOWING_MODE_PINNED) { 392 continue; 393 } 394 // No default animation for this, so just update bounds/position. 395 final int rootIdx = TransitionUtil.rootIndexFor(change, info); 396 startTransaction.setPosition(change.getLeash(), 397 change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x, 398 change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y); 399 // Seamless display transition doesn't need to animate. 400 if (isSeamlessDisplayChange) continue; 401 if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) 402 && !change.hasFlags(FLAG_FILLS_TASK))) { 403 // Update Task and embedded split window crop bounds, otherwise we may see crop 404 // on previous bounds during the rotation animation. 405 startTransaction.setWindowCrop(change.getLeash(), 406 change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); 407 } 408 // Rotation change of independent non display window container. 409 if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY) 410 && change.getStartRotation() != change.getEndRotation()) { 411 startRotationAnimation(startTransaction, change, info, 412 ROTATION_ANIMATION_ROTATE, animations, onAnimFinish); 413 continue; 414 } 415 } 416 417 // Hide the invisible surface directly without animating it if there is a display 418 // rotation animation playing. 419 if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(mode)) { 420 startTransaction.hide(change.getLeash()); 421 continue; 422 } 423 424 // The back gesture has animated this change before transition happen, so here we don't 425 // play the animation again. 426 if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) { 427 continue; 428 } 429 // Don't animate anything that isn't independent. 430 if (!TransitionInfo.isIndependent(change, info)) continue; 431 432 Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition); 433 if (a != null) { 434 if (isTask) { 435 final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0; 436 if (!isTranslucent && TransitionUtil.isOpenOrCloseMode(mode) 437 && TransitionUtil.isOpenOrCloseMode(info.getType()) 438 && wallpaperTransit == WALLPAPER_TRANSITION_NONE) { 439 // Use the overview background as the background for the animation 440 final Context uiContext = ActivityThread.currentActivityThread() 441 .getSystemUiContext(); 442 backgroundColorForTransition = 443 uiContext.getColor(R.color.overview_background); 444 } 445 if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN 446 && TransitionUtil.isOpeningType(info.getType())) { 447 // Need to flip the z-order of opening/closing because the WALLPAPER_OPEN 448 // always animates the closing task over the opening one while 449 // traditionally, an OPEN transition animates the opening over the closing. 450 451 // See Transitions#setupAnimHierarchy for details about these variables. 452 final int numChanges = info.getChanges().size(); 453 final int zSplitLine = numChanges + 1; 454 if (TransitionUtil.isOpeningType(mode)) { 455 final int layer = zSplitLine - i; 456 startTransaction.setLayer(change.getLeash(), layer); 457 } else if (TransitionUtil.isClosingType(mode)) { 458 final int layer = zSplitLine + numChanges - i; 459 startTransaction.setLayer(change.getLeash(), layer); 460 } 461 } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType()) 462 && TransitionUtil.isClosingType(mode)) { 463 // If there is a closing translucent task in an OPENING transition, we will 464 // actually select a CLOSING animation, so move the closing task into 465 // the animating part of the z-order. 466 467 // See Transitions#setupAnimHierarchy for details about these variables. 468 final int numChanges = info.getChanges().size(); 469 final int zSplitLine = numChanges + 1; 470 final int layer = zSplitLine + numChanges - i; 471 startTransaction.setLayer(change.getLeash(), layer); 472 } 473 } 474 475 final float cornerRadius; 476 if (a.hasRoundedCorners() && isTask) { 477 // hasRoundedCorners is currently only enabled for tasks 478 final Context displayContext = 479 mDisplayController.getDisplayContext(change.getTaskInfo().displayId); 480 cornerRadius = displayContext == null ? 0 481 : ScreenDecorationsUtils.getWindowCornerRadius(displayContext); 482 } else { 483 cornerRadius = 0; 484 } 485 486 backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a, 487 backgroundColorForTransition); 488 489 if (!isTask && a.hasExtension()) { 490 if (!TransitionUtil.isOpeningType(mode)) { 491 // Can screenshot now (before startTransaction is applied) 492 edgeExtendWindow(change, a, startTransaction, finishTransaction); 493 } else { 494 // Need to screenshot after startTransaction is applied otherwise activity 495 // may not be visible or ready yet. 496 postStartTransactionCallbacks 497 .add(t -> edgeExtendWindow(change, a, t, finishTransaction)); 498 } 499 } 500 501 final Rect clipRect = TransitionUtil.isClosingType(mode) 502 ? new Rect(mRotator.getEndBoundsInStartRotation(change)) 503 : new Rect(change.getEndAbsBounds()); 504 clipRect.offsetTo(0, 0); 505 506 buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, 507 mTransactionPool, mMainExecutor, change.getEndRelOffset(), cornerRadius, 508 clipRect); 509 510 if (info.getAnimationOptions() != null) { 511 attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(), 512 cornerRadius); 513 } 514 } 515 } 516 517 if (backgroundColorForTransition != 0) { 518 addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction, 519 finishTransaction); 520 } 521 522 if (postStartTransactionCallbacks.size() > 0) { 523 // postStartTransactionCallbacks require that the start transaction is already 524 // applied to run otherwise they may result in flickers and UI inconsistencies. 525 startTransaction.apply(true /* sync */); 526 // startTransaction is empty now, so fill it with the edge-extension setup 527 for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback : 528 postStartTransactionCallbacks) { 529 postStartTransactionCallback.accept(startTransaction); 530 } 531 } 532 startTransaction.apply(); 533 534 // now start animations. they are started on another thread, so we have to post them 535 // *after* applying the startTransaction 536 mAnimExecutor.execute(() -> { 537 for (int i = 0; i < animations.size(); ++i) { 538 animations.get(i).start(); 539 } 540 }); 541 542 mRotator.cleanUp(finishTransaction); 543 TransitionMetrics.getInstance().reportAnimationStart(transition); 544 // run finish now in-case there are no animations 545 onAnimFinish.run(); 546 return true; 547 } 548 addBackgroundColorOnTDA(@onNull TransitionInfo info, @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)549 private void addBackgroundColorOnTDA(@NonNull TransitionInfo info, 550 @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction, 551 @NonNull SurfaceControl.Transaction finishTransaction) { 552 final Color bgColor = Color.valueOf(color); 553 final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() }; 554 555 for (int i = 0; i < info.getRootCount(); ++i) { 556 final int displayId = info.getRoot(i).getDisplayId(); 557 final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder() 558 .setName("animation-background") 559 .setCallsite("DefaultTransitionHandler") 560 .setColorLayer(); 561 562 mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder); 563 final SurfaceControl backgroundSurface = colorLayerBuilder.build(); 564 startTransaction.setColor(backgroundSurface, colorArray) 565 .setLayer(backgroundSurface, -1) 566 .show(backgroundSurface); 567 finishTransaction.remove(backgroundSurface); 568 } 569 } 570 isDreamTransition(@onNull TransitionInfo info)571 private static boolean isDreamTransition(@NonNull TransitionInfo info) { 572 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 573 final TransitionInfo.Change change = info.getChanges().get(i); 574 if (change.getTaskInfo() != null 575 && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM) { 576 return true; 577 } 578 } 579 580 return false; 581 } 582 583 /** 584 * Does `info` only contain translucent visibility changes (CHANGEs are ignored). We select 585 * different animations and z-orders for these 586 */ isOnlyTranslucent(@onNull TransitionInfo info)587 private static boolean isOnlyTranslucent(@NonNull TransitionInfo info) { 588 int translucentOpen = 0; 589 int translucentClose = 0; 590 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 591 final TransitionInfo.Change change = info.getChanges().get(i); 592 if (change.getMode() == TRANSIT_CHANGE) continue; 593 if (change.hasFlags(FLAG_TRANSLUCENT)) { 594 if (TransitionUtil.isOpeningType(change.getMode())) { 595 translucentOpen += 1; 596 } else { 597 translucentClose += 1; 598 } 599 } else { 600 return false; 601 } 602 } 603 return (translucentOpen + translucentClose) > 0; 604 } 605 606 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)607 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 608 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, 609 @NonNull Transitions.TransitionFinishCallback finishCallback) { 610 ArrayList<Animator> anims = mAnimations.get(mergeTarget); 611 if (anims == null) return; 612 for (int i = anims.size() - 1; i >= 0; --i) { 613 final Animator anim = anims.get(i); 614 mAnimExecutor.execute(anim::end); 615 } 616 } 617 startRotationAnimation(SurfaceControl.Transaction startTransaction, TransitionInfo.Change change, TransitionInfo info, int animHint, ArrayList<Animator> animations, Runnable onAnimFinish)618 private void startRotationAnimation(SurfaceControl.Transaction startTransaction, 619 TransitionInfo.Change change, TransitionInfo info, int animHint, 620 ArrayList<Animator> animations, Runnable onAnimFinish) { 621 final int rootIdx = TransitionUtil.rootIndexFor(change, info); 622 final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession, 623 mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(), 624 animHint); 625 // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real 626 // content, and background color. The item of "animGroup" will be removed if the sub 627 // animation is finished. Then if the list becomes empty, the rotation animation is done. 628 final ArrayList<Animator> animGroup = new ArrayList<>(3); 629 final ArrayList<Animator> animGroupStore = new ArrayList<>(3); 630 final Runnable finishCallback = () -> { 631 if (!animGroup.isEmpty()) return; 632 anim.kill(); 633 animations.removeAll(animGroupStore); 634 onAnimFinish.run(); 635 }; 636 anim.buildAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting, 637 mMainExecutor); 638 for (int i = animGroup.size() - 1; i >= 0; i--) { 639 final Animator animator = animGroup.get(i); 640 animGroupStore.add(animator); 641 animations.add(animator); 642 } 643 } 644 645 @Nullable 646 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)647 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 648 @NonNull TransitionRequestInfo request) { 649 return null; 650 } 651 652 @Override setAnimScaleSetting(float scale)653 public void setAnimScaleSetting(float scale) { 654 mTransitionAnimationScaleSetting = scale; 655 } 656 657 @Nullable loadAnimation(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, int wallpaperTransit, boolean isDreamTransition)658 private Animation loadAnimation(@NonNull TransitionInfo info, 659 @NonNull TransitionInfo.Change change, int wallpaperTransit, 660 boolean isDreamTransition) { 661 Animation a; 662 663 final int type = info.getType(); 664 final int flags = info.getFlags(); 665 final int changeMode = change.getMode(); 666 final int changeFlags = change.getFlags(); 667 final boolean isOpeningType = TransitionUtil.isOpeningType(type); 668 final boolean enter = TransitionUtil.isOpeningType(changeMode); 669 final boolean isTask = change.getTaskInfo() != null; 670 final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); 671 final int overrideType = options != null ? options.getType() : ANIM_NONE; 672 final Rect endBounds = TransitionUtil.isClosingType(changeMode) 673 ? mRotator.getEndBoundsInStartRotation(change) 674 : change.getEndAbsBounds(); 675 676 if (info.isKeyguardGoingAway()) { 677 a = mTransitionAnimation.loadKeyguardExitAnimation(flags, 678 (changeFlags & FLAG_SHOW_WALLPAPER) != 0); 679 } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) { 680 a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(); 681 } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) { 682 if (isOpeningType) { 683 a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter); 684 } else { 685 a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter); 686 } 687 } else if (changeMode == TRANSIT_CHANGE) { 688 // In the absence of a specific adapter, we just want to keep everything stationary. 689 a = new AlphaAnimation(1.f, 1.f); 690 a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION); 691 } else if (type == TRANSIT_RELAUNCH) { 692 a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds); 693 } else if (overrideType == ANIM_CUSTOM 694 && (!isTask || options.getOverrideTaskTransition())) { 695 a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter 696 ? options.getEnterResId() : options.getExitResId()); 697 } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) { 698 a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(); 699 } else if (overrideType == ANIM_CLIP_REVEAL) { 700 a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter, 701 endBounds, endBounds, options.getTransitionBounds()); 702 } else if (overrideType == ANIM_SCALE_UP) { 703 a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter, 704 endBounds, options.getTransitionBounds()); 705 } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP 706 || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) { 707 final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP; 708 a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp, 709 endBounds, type, wallpaperTransit, options.getThumbnail(), 710 options.getTransitionBounds()); 711 } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) { 712 // This received a transferred starting window, so don't animate 713 return null; 714 } else if (overrideType == ANIM_SCENE_TRANSITION) { 715 // If there's a scene-transition, then jump-cut. 716 return null; 717 } else { 718 a = loadAttributeAnimation( 719 info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition); 720 } 721 722 if (a != null) { 723 if (!a.isInitialized()) { 724 final Rect animationRange = TransitionUtil.isClosingType(changeMode) 725 ? change.getStartAbsBounds() : change.getEndAbsBounds(); 726 a.initialize(animationRange.width(), animationRange.height(), 727 endBounds.width(), endBounds.height()); 728 } 729 a.restrictDuration(MAX_ANIMATION_DURATION); 730 a.scaleCurrentDuration(mTransitionAnimationScaleSetting); 731 } 732 return a; 733 } 734 735 /** Builds an animator for the surface and adds it to the `animations` list. */ buildSurfaceAnimation(@onNull ArrayList<Animator> animations, @NonNull Animation anim, @NonNull SurfaceControl leash, @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, @Nullable Rect clipRect)736 static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations, 737 @NonNull Animation anim, @NonNull SurfaceControl leash, 738 @NonNull Runnable finishCallback, @NonNull TransactionPool pool, 739 @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, 740 @Nullable Rect clipRect) { 741 final SurfaceControl.Transaction transaction = pool.acquire(); 742 final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); 743 final Transformation transformation = new Transformation(); 744 final float[] matrix = new float[9]; 745 // Animation length is already expected to be scaled. 746 va.overrideDurationScale(1.0f); 747 va.setDuration(anim.computeDurationHint()); 748 final ValueAnimator.AnimatorUpdateListener updateListener = animation -> { 749 final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); 750 751 applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix, 752 position, cornerRadius, clipRect); 753 }; 754 va.addUpdateListener(updateListener); 755 756 final Runnable finisher = () -> { 757 applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix, 758 position, cornerRadius, clipRect); 759 760 pool.release(transaction); 761 mainExecutor.execute(() -> { 762 animations.remove(va); 763 finishCallback.run(); 764 }); 765 }; 766 va.addListener(new AnimatorListenerAdapter() { 767 // It is possible for the end/cancel to be called more than once, which may cause 768 // issues if the animating surface has already been released. Track the finished 769 // state here to skip duplicate callbacks. See b/252872225. 770 private boolean mFinished = false; 771 772 @Override 773 public void onAnimationEnd(Animator animation) { 774 onFinish(); 775 } 776 777 @Override 778 public void onAnimationCancel(Animator animation) { 779 onFinish(); 780 } 781 782 private void onFinish() { 783 if (mFinished) return; 784 mFinished = true; 785 finisher.run(); 786 // The update listener can continue to be called after the animation has ended if 787 // end() is called manually again before the finisher removes the animation. 788 // Remove it manually here to prevent animating a released surface. 789 // See b/252872225. 790 va.removeUpdateListener(updateListener); 791 } 792 }); 793 animations.add(va); 794 } 795 attachThumbnail(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options, float cornerRadius)796 private void attachThumbnail(@NonNull ArrayList<Animator> animations, 797 @NonNull Runnable finishCallback, TransitionInfo.Change change, 798 TransitionInfo.AnimationOptions options, float cornerRadius) { 799 final boolean isOpen = TransitionUtil.isOpeningType(change.getMode()); 800 final boolean isClose = TransitionUtil.isClosingType(change.getMode()); 801 if (isOpen) { 802 if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) { 803 attachCrossProfileThumbnailAnimation(animations, finishCallback, change, 804 cornerRadius); 805 } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) { 806 attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius); 807 } 808 } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) { 809 attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius); 810 } 811 } 812 attachCrossProfileThumbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius)813 private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations, 814 @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) { 815 final Rect bounds = change.getEndAbsBounds(); 816 // Show the right drawable depending on the user we're transitioning to. 817 final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL) 818 ? mContext.getDrawable(R.drawable.ic_account_circle) 819 : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL) 820 ? mEnterpriseThumbnailDrawable : null; 821 if (thumbnailDrawable == null) { 822 return; 823 } 824 final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail( 825 thumbnailDrawable, bounds); 826 if (thumbnail == null) { 827 return; 828 } 829 830 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 831 final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession, 832 change.getLeash(), thumbnail, transaction); 833 final Animation a = 834 mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds); 835 if (a == null) { 836 return; 837 } 838 839 final Runnable finisher = () -> { 840 wt.destroy(transaction); 841 mTransactionPool.release(transaction); 842 843 finishCallback.run(); 844 }; 845 a.restrictDuration(MAX_ANIMATION_DURATION); 846 a.scaleCurrentDuration(mTransitionAnimationScaleSetting); 847 buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, 848 mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); 849 } 850 attachThumbnailAnimation(@onNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options, float cornerRadius)851 private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations, 852 @NonNull Runnable finishCallback, TransitionInfo.Change change, 853 TransitionInfo.AnimationOptions options, float cornerRadius) { 854 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 855 final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession, 856 change.getLeash(), options.getThumbnail(), transaction); 857 final Rect bounds = change.getEndAbsBounds(); 858 final int orientation = mContext.getResources().getConfiguration().orientation; 859 final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds, 860 mInsets, options.getThumbnail(), orientation, null /* startRect */, 861 options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP); 862 863 final Runnable finisher = () -> { 864 wt.destroy(transaction); 865 mTransactionPool.release(transaction); 866 867 finishCallback.run(); 868 }; 869 a.restrictDuration(MAX_ANIMATION_DURATION); 870 a.scaleCurrentDuration(mTransitionAnimationScaleSetting); 871 buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, 872 mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); 873 } 874 getWallpaperTransitType(TransitionInfo info)875 private static int getWallpaperTransitType(TransitionInfo info) { 876 boolean hasOpenWallpaper = false; 877 boolean hasCloseWallpaper = false; 878 879 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 880 final TransitionInfo.Change change = info.getChanges().get(i); 881 if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) { 882 if (TransitionUtil.isOpeningType(change.getMode())) { 883 hasOpenWallpaper = true; 884 } else if (TransitionUtil.isClosingType(change.getMode())) { 885 hasCloseWallpaper = true; 886 } 887 } 888 } 889 890 if (hasOpenWallpaper && hasCloseWallpaper) { 891 return TransitionUtil.isOpeningType(info.getType()) 892 ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE; 893 } else if (hasOpenWallpaper) { 894 return WALLPAPER_TRANSITION_OPEN; 895 } else if (hasCloseWallpaper) { 896 return WALLPAPER_TRANSITION_CLOSE; 897 } else { 898 return WALLPAPER_TRANSITION_NONE; 899 } 900 } 901 902 /** 903 * Returns {@code true} if the default transition handler can run the override animation. 904 * @see #loadAnimation(TransitionInfo, TransitionInfo.Change, int, boolean) 905 */ isSupportedOverrideAnimation( @onNull TransitionInfo.AnimationOptions options)906 public static boolean isSupportedOverrideAnimation( 907 @NonNull TransitionInfo.AnimationOptions options) { 908 final int animType = options.getType(); 909 return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP 910 || animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN 911 || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS; 912 } 913 applyTransformation(long time, SurfaceControl.Transaction t, SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix, Point position, float cornerRadius, @Nullable Rect immutableClipRect)914 private static void applyTransformation(long time, SurfaceControl.Transaction t, 915 SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix, 916 Point position, float cornerRadius, @Nullable Rect immutableClipRect) { 917 tmpTransformation.clear(); 918 anim.getTransformation(time, tmpTransformation); 919 if (position != null) { 920 tmpTransformation.getMatrix().postTranslate(position.x, position.y); 921 } 922 t.setMatrix(leash, tmpTransformation.getMatrix(), matrix); 923 t.setAlpha(leash, tmpTransformation.getAlpha()); 924 925 final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect); 926 Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE); 927 if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) { 928 // Clip out any overflowing edge extension 929 clipRect.inset(extensionInsets); 930 t.setCrop(leash, clipRect); 931 } 932 933 if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) { 934 // We can only apply rounded corner if a crop is set 935 t.setCrop(leash, clipRect); 936 t.setCornerRadius(leash, cornerRadius); 937 } 938 939 t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 940 t.apply(); 941 } 942 } 943