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