1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.activityembedding; 18 19 import static android.view.WindowManager.TRANSIT_CHANGE; 20 import static android.view.WindowManager.TRANSIT_CLOSE; 21 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; 22 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; 23 24 import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation; 25 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; 26 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; 27 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; 28 29 import android.animation.Animator; 30 import android.animation.ValueAnimator; 31 import android.content.Context; 32 import android.graphics.Rect; 33 import android.os.IBinder; 34 import android.util.ArraySet; 35 import android.util.Log; 36 import android.view.Choreographer; 37 import android.view.SurfaceControl; 38 import android.view.animation.Animation; 39 import android.window.TransitionInfo; 40 import android.window.WindowContainerToken; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter; 47 import com.android.wm.shell.common.ScreenshotUtils; 48 import com.android.wm.shell.util.TransitionUtil; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Set; 53 import java.util.function.Consumer; 54 55 /** To run the ActivityEmbedding animations. */ 56 class ActivityEmbeddingAnimationRunner { 57 58 private static final String TAG = "ActivityEmbeddingAnimR"; 59 60 private final ActivityEmbeddingController mController; 61 @VisibleForTesting 62 final ActivityEmbeddingAnimationSpec mAnimationSpec; 63 64 @Nullable 65 private Animator mActiveAnimator; 66 ActivityEmbeddingAnimationRunner(@onNull Context context, @NonNull ActivityEmbeddingController controller)67 ActivityEmbeddingAnimationRunner(@NonNull Context context, 68 @NonNull ActivityEmbeddingController controller) { 69 mController = controller; 70 mAnimationSpec = new ActivityEmbeddingAnimationSpec(context); 71 } 72 73 /** Creates and starts animation for ActivityEmbedding transition. */ startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)74 void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 75 @NonNull SurfaceControl.Transaction startTransaction, 76 @NonNull SurfaceControl.Transaction finishTransaction) { 77 // There may be some surface change that we want to apply after the start transaction is 78 // applied to make sure the surface is ready. 79 final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = 80 new ArrayList<>(); 81 final Animator animator = createAnimator(info, startTransaction, 82 finishTransaction, 83 () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks); 84 mActiveAnimator = animator; 85 86 // Start the animation. 87 if (!postStartTransactionCallbacks.isEmpty()) { 88 // postStartTransactionCallbacks require that the start transaction is already 89 // applied to run otherwise they may result in flickers and UI inconsistencies. 90 startTransaction.apply(true /* sync */); 91 92 // Run tasks that require startTransaction to already be applied 93 final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 94 for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback : 95 postStartTransactionCallbacks) { 96 postStartTransactionCallback.accept(t); 97 } 98 t.apply(); 99 animator.start(); 100 } else { 101 startTransaction.apply(); 102 animator.start(); 103 } 104 } 105 cancelAnimationFromMerge()106 void cancelAnimationFromMerge() { 107 if (mActiveAnimator == null) { 108 Log.e(TAG, 109 "No active ActivityEmbedding animator running but mergeAnimation is " 110 + "trying to cancel one." 111 ); 112 return; 113 } 114 mActiveAnimator.end(); 115 } 116 117 /** 118 * Sets transition animation scale settings value. 119 * @param scale The setting value of transition animation scale. 120 */ setAnimScaleSetting(float scale)121 void setAnimScaleSetting(float scale) { 122 mAnimationSpec.setAnimScaleSetting(scale); 123 } 124 125 /** Creates the animator for the given {@link TransitionInfo}. */ 126 @VisibleForTesting 127 @NonNull createAnimator(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Runnable animationFinishCallback, @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks)128 Animator createAnimator(@NonNull TransitionInfo info, 129 @NonNull SurfaceControl.Transaction startTransaction, 130 @NonNull SurfaceControl.Transaction finishTransaction, 131 @NonNull Runnable animationFinishCallback, 132 @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) { 133 final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info, 134 startTransaction); 135 final ValueAnimator animator = ValueAnimator.ofFloat(0, 1); 136 long duration = 0; 137 if (adapters.isEmpty()) { 138 // Jump cut 139 // No need to modify the animator, but to update the startTransaction with the changes' 140 // ending states. 141 prepareForJumpCut(info, startTransaction); 142 } else { 143 addEdgeExtensionIfNeeded(startTransaction, finishTransaction, 144 postStartTransactionCallbacks, adapters); 145 addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters); 146 for (ActivityEmbeddingAnimationAdapter adapter : adapters) { 147 duration = Math.max(duration, adapter.getDurationHint()); 148 } 149 animator.addUpdateListener((anim) -> { 150 // Update all adapters in the same transaction. 151 final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 152 t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 153 for (ActivityEmbeddingAnimationAdapter adapter : adapters) { 154 adapter.onAnimationUpdate(t, animator.getCurrentPlayTime()); 155 } 156 t.apply(); 157 }); 158 prepareForFirstFrame(startTransaction, adapters); 159 } 160 animator.setDuration(duration); 161 animator.addListener(new Animator.AnimatorListener() { 162 @Override 163 public void onAnimationStart(Animator animation) {} 164 165 @Override 166 public void onAnimationEnd(Animator animation) { 167 final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 168 for (ActivityEmbeddingAnimationAdapter adapter : adapters) { 169 adapter.onAnimationEnd(t); 170 } 171 t.apply(); 172 mActiveAnimator = null; 173 animationFinishCallback.run(); 174 } 175 176 @Override 177 public void onAnimationCancel(Animator animation) {} 178 179 @Override 180 public void onAnimationRepeat(Animator animation) {} 181 }); 182 return animator; 183 } 184 185 /** 186 * Creates list of {@link ActivityEmbeddingAnimationAdapter} to handle animations on all window 187 * changes. 188 */ 189 @NonNull createAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)190 private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( 191 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { 192 boolean isChangeTransition = false; 193 for (TransitionInfo.Change change : info.getChanges()) { 194 if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) { 195 // Skip the animation if the windows are behind an app starting window. 196 return new ArrayList<>(); 197 } 198 if (!isChangeTransition && change.getMode() == TRANSIT_CHANGE 199 && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { 200 isChangeTransition = true; 201 } 202 } 203 if (isChangeTransition) { 204 return createChangeAnimationAdapters(info, startTransaction); 205 } 206 if (TransitionUtil.isClosingType(info.getType())) { 207 return createCloseAnimationAdapters(info, startTransaction); 208 } 209 return createOpenAnimationAdapters(info, startTransaction); 210 } 211 212 @NonNull createOpenAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)213 private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters( 214 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { 215 return createOpenCloseAnimationAdapters(info, true /* isOpening */, 216 mAnimationSpec::loadOpenAnimation, startTransaction); 217 } 218 219 @NonNull createCloseAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)220 private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters( 221 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { 222 return createOpenCloseAnimationAdapters(info, false /* isOpening */, 223 mAnimationSpec::loadCloseAnimation, startTransaction); 224 } 225 226 /** 227 * Creates {@link ActivityEmbeddingAnimationAdapter} for OPEN and CLOSE types of transition. 228 * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type. 229 */ 230 @NonNull createOpenCloseAnimationAdapters( @onNull TransitionInfo info, boolean isOpening, @NonNull AnimationProvider animationProvider, @NonNull SurfaceControl.Transaction startTransaction)231 private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters( 232 @NonNull TransitionInfo info, boolean isOpening, 233 @NonNull AnimationProvider animationProvider, 234 @NonNull SurfaceControl.Transaction startTransaction) { 235 // We need to know if the change window is only a partial of the whole animation screen. 236 // If so, we will need to adjust it to make the whole animation screen looks like one. 237 final List<TransitionInfo.Change> openingChanges = new ArrayList<>(); 238 final List<TransitionInfo.Change> closingChanges = new ArrayList<>(); 239 final Rect openingWholeScreenBounds = new Rect(); 240 final Rect closingWholeScreenBounds = new Rect(); 241 for (TransitionInfo.Change change : info.getChanges()) { 242 if (TransitionUtil.isOpeningType(change.getMode())) { 243 openingChanges.add(change); 244 openingWholeScreenBounds.union(change.getEndAbsBounds()); 245 } else { 246 closingChanges.add(change); 247 // Also union with the start bounds because the closing transition may be shrunk. 248 closingWholeScreenBounds.union(change.getStartAbsBounds()); 249 closingWholeScreenBounds.union(change.getEndAbsBounds()); 250 } 251 } 252 253 // For OPEN transition, open windows should be above close windows. 254 // For CLOSE transition, open windows should be below close windows. 255 int offsetLayer = TYPE_LAYER_OFFSET; 256 final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); 257 for (TransitionInfo.Change change : openingChanges) { 258 final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( 259 info, change, animationProvider, openingWholeScreenBounds); 260 if (isOpening) { 261 adapter.overrideLayer(offsetLayer++); 262 } 263 adapters.add(adapter); 264 } 265 for (TransitionInfo.Change change : closingChanges) { 266 if (shouldUseSnapshotAnimationForClosingChange(change)) { 267 SurfaceControl screenshot = getOrCreateScreenshot(change, change, startTransaction); 268 if (screenshot != null) { 269 final SnapshotAdapter snapshotAdapter = new SnapshotAdapter( 270 createShowSnapshotForClosingAnimation(), change, screenshot, 271 TransitionUtil.getRootFor(change, info)); 272 if (!isOpening) { 273 snapshotAdapter.overrideLayer(offsetLayer++); 274 } 275 adapters.add(snapshotAdapter); 276 } 277 } 278 final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( 279 info, change, animationProvider, closingWholeScreenBounds); 280 if (!isOpening) { 281 adapter.overrideLayer(offsetLayer++); 282 } 283 adapters.add(adapter); 284 } 285 return adapters; 286 } 287 288 /** 289 * Returns whether we should use snapshot animation for the closing change. 290 * It's usually because the end bounds of the closing change are shrunk, which leaves a black 291 * area in the transition. 292 */ shouldUseSnapshotAnimationForClosingChange( @onNull TransitionInfo.Change closingChange)293 static boolean shouldUseSnapshotAnimationForClosingChange( 294 @NonNull TransitionInfo.Change closingChange) { 295 // Only check closing type because we only take screenshot for closing bounds-changing 296 // changes. 297 if (!TransitionUtil.isClosingType(closingChange.getMode())) { 298 return false; 299 } 300 // Don't need to take screenshot if there's no bounds change. 301 return !closingChange.getStartAbsBounds().equals(closingChange.getEndAbsBounds()); 302 } 303 304 /** Sets the first frame to the {@code startTransaction} to avoid any flicker on start. */ prepareForFirstFrame(@onNull SurfaceControl.Transaction startTransaction, @NonNull List<ActivityEmbeddingAnimationAdapter> adapters)305 private void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction, 306 @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { 307 startTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 308 for (ActivityEmbeddingAnimationAdapter adapter : adapters) { 309 adapter.prepareForFirstFrame(startTransaction); 310 } 311 } 312 313 /** Adds edge extension to the surfaces that have such an animation property. */ addEdgeExtensionIfNeeded(@onNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks, @NonNull List<ActivityEmbeddingAnimationAdapter> adapters)314 private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction, 315 @NonNull SurfaceControl.Transaction finishTransaction, 316 @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks, 317 @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { 318 for (ActivityEmbeddingAnimationAdapter adapter : adapters) { 319 final Animation animation = adapter.mAnimation; 320 if (!animation.hasExtension()) { 321 continue; 322 } 323 final TransitionInfo.Change change = adapter.mChange; 324 if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) { 325 // Need to screenshot after startTransaction is applied otherwise activity 326 // may not be visible or ready yet. 327 postStartTransactionCallbacks.add( 328 t -> edgeExtendWindow(change, animation, t, finishTransaction)); 329 } else { 330 // Can screenshot now (before startTransaction is applied) 331 edgeExtendWindow(change, animation, startTransaction, finishTransaction); 332 } 333 } 334 } 335 336 /** Adds background color to the transition if any animation has such a property. */ addBackgroundColorIfNeeded(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull List<ActivityEmbeddingAnimationAdapter> adapters)337 private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info, 338 @NonNull SurfaceControl.Transaction startTransaction, 339 @NonNull SurfaceControl.Transaction finishTransaction, 340 @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { 341 for (ActivityEmbeddingAnimationAdapter adapter : adapters) { 342 final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange, 343 adapter.mAnimation, 0 /* defaultColor */); 344 if (backgroundColor != 0) { 345 // We only need to show one color. 346 addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction, 347 finishTransaction); 348 return; 349 } 350 } 351 } 352 353 @NonNull createOpenCloseAnimationAdapter( @onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds)354 private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter( 355 @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, 356 @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) { 357 final Animation animation = animationProvider.get(info, change, wholeAnimationBounds); 358 return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(), 359 wholeAnimationBounds, TransitionUtil.getRootFor(change, info)); 360 } 361 362 @NonNull createChangeAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)363 private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters( 364 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { 365 if (shouldUseJumpCutForChangeTransition(info)) { 366 return new ArrayList<>(); 367 } 368 369 final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); 370 final Set<TransitionInfo.Change> handledChanges = new ArraySet<>(); 371 372 // For the first iteration, we prepare the animation for the change type windows. This is 373 // needed because there may be window that is reparented while resizing. In such case, we 374 // will do the following: 375 // 1. Capture a screenshot from the Activity surface. 376 // 2. Attach the screenshot surface to the top of TaskFragment (Activity's parent) surface. 377 // 3. Animate the TaskFragment using Activity Change info (start/end bounds). 378 // This is because the TaskFragment surface/change won't contain the Activity's before its 379 // reparent. 380 Animation changeAnimation = null; 381 Rect parentBounds = new Rect(); 382 for (TransitionInfo.Change change : info.getChanges()) { 383 if (change.getMode() != TRANSIT_CHANGE 384 || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { 385 continue; 386 } 387 388 // This is the window with bounds change. 389 handledChanges.add(change); 390 final WindowContainerToken parentToken = change.getParent(); 391 TransitionInfo.Change boundsAnimationChange = change; 392 if (parentToken != null) { 393 // When the parent window is also included in the transition as an opening window, 394 // we would like to animate the parent window instead. 395 final TransitionInfo.Change parentChange = info.getChange(parentToken); 396 if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) { 397 // We won't create a separate animation for the parent, but to animate the 398 // parent for the child resizing. 399 handledChanges.add(parentChange); 400 boundsAnimationChange = parentChange; 401 } 402 } 403 404 // The TaskFragment may be enter/exit split, so we take the union of both as the parent 405 // size. 406 parentBounds.union(boundsAnimationChange.getStartAbsBounds()); 407 parentBounds.union(boundsAnimationChange.getEndAbsBounds()); 408 if (boundsAnimationChange != change) { 409 // Union the change starting bounds in case the activity is resized and reparented 410 // to a TaskFragment. In that case, the TaskFragment may not cover the activity's 411 // starting bounds. 412 parentBounds.union(change.getStartAbsBounds()); 413 } 414 415 // There are two animations in the array. The first one is for the start leash 416 // (snapshot), and the second one is for the end leash (TaskFragment). 417 final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, 418 parentBounds); 419 // Keep track as we might need to add background color for the animation. 420 // Although there may be multiple change animation, record one of them is sufficient 421 // because the background color will be added to the root leash for the whole animation. 422 changeAnimation = animations[1]; 423 424 // Create a screenshot based on change, but attach it to the top of the 425 // boundsAnimationChange. 426 final SurfaceControl screenshotLeash = getOrCreateScreenshot(change, 427 boundsAnimationChange, startTransaction); 428 final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info); 429 if (screenshotLeash != null) { 430 // Adapter for the starting screenshot leash. 431 // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd 432 adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( 433 animations[0], change, screenshotLeash, root)); 434 } else { 435 Log.e(TAG, "Failed to take screenshot for change=" + change); 436 } 437 // Adapter for the ending bounds changed leash. 438 adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( 439 animations[1], boundsAnimationChange, root)); 440 } 441 442 if (parentBounds.isEmpty()) { 443 throw new IllegalStateException( 444 "There should be at least one changing window to play the change animation"); 445 } 446 447 // If there is no corresponding open/close window with the change, we should show background 448 // color to cover the empty part of the screen. 449 boolean shouldShouldBackgroundColor = true; 450 // Handle the other windows that don't have bounds change in the same transition. 451 for (TransitionInfo.Change change : info.getChanges()) { 452 if (handledChanges.contains(change)) { 453 // Skip windows that we have already handled in the previous iteration. 454 continue; 455 } 456 457 final Animation animation; 458 if ((change.getParent() != null 459 && handledChanges.contains(info.getChange(change.getParent()))) 460 || change.getMode() == TRANSIT_CHANGE) { 461 // No-op if it will be covered by the changing parent window, or it is a changing 462 // window without bounds change. 463 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); 464 } else if (TransitionUtil.isClosingType(change.getMode())) { 465 animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); 466 shouldShouldBackgroundColor = false; 467 } else { 468 animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); 469 shouldShouldBackgroundColor = false; 470 } 471 adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, 472 TransitionUtil.getRootFor(change, info))); 473 } 474 475 if (shouldShouldBackgroundColor && changeAnimation != null) { 476 // Change animation may leave part of the screen empty. Show background color to cover 477 // that. 478 changeAnimation.setShowBackdrop(true); 479 } 480 481 return adapters; 482 } 483 484 /** 485 * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. 486 * The screenshot leash should be attached to the {@code animationChange} surface which we will 487 * animate later. 488 */ 489 @Nullable getOrCreateScreenshot(@onNull TransitionInfo.Change screenshotChange, @NonNull TransitionInfo.Change animationChange, @NonNull SurfaceControl.Transaction t)490 private SurfaceControl getOrCreateScreenshot(@NonNull TransitionInfo.Change screenshotChange, 491 @NonNull TransitionInfo.Change animationChange, 492 @NonNull SurfaceControl.Transaction t) { 493 final SurfaceControl screenshotLeash = screenshotChange.getSnapshot(); 494 if (screenshotLeash != null) { 495 // If WM Core has already taken a screenshot, make sure it is reparented to the 496 // animation leash. 497 t.reparent(screenshotLeash, animationChange.getLeash()); 498 return screenshotLeash; 499 } 500 501 // If WM Core hasn't taken a screenshot, take a screenshot now. 502 final Rect cropBounds = new Rect(screenshotChange.getStartAbsBounds()); 503 cropBounds.offsetTo(0, 0); 504 return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(), 505 animationChange.getLeash(), cropBounds, Integer.MAX_VALUE); 506 } 507 508 /** 509 * Whether we should use jump cut for the change transition. 510 * This normally happens when opening a new secondary with the existing primary using a 511 * different split layout. This can be complicated, like from horizontal to vertical split with 512 * new split pairs. 513 * Uses a jump cut animation to simplify. 514 */ shouldUseJumpCutForChangeTransition(@onNull TransitionInfo info)515 private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) { 516 // There can be reparenting of changing Activity to new open TaskFragment, so we need to 517 // exclude both in the first iteration. 518 final List<TransitionInfo.Change> changingChanges = new ArrayList<>(); 519 for (TransitionInfo.Change change : info.getChanges()) { 520 if (change.getMode() != TRANSIT_CHANGE 521 || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { 522 continue; 523 } 524 changingChanges.add(change); 525 final WindowContainerToken parentToken = change.getParent(); 526 if (parentToken != null) { 527 // When the parent window is also included in the transition as an opening window, 528 // we would like to animate the parent window instead. 529 final TransitionInfo.Change parentChange = info.getChange(parentToken); 530 if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) { 531 changingChanges.add(parentChange); 532 } 533 } 534 } 535 if (changingChanges.isEmpty()) { 536 // No changing target found. 537 return true; 538 } 539 540 // Check if the transition contains both opening and closing windows. 541 boolean hasOpeningWindow = false; 542 boolean hasClosingWindow = false; 543 for (TransitionInfo.Change change : info.getChanges()) { 544 if (changingChanges.contains(change)) { 545 continue; 546 } 547 if (change.getParent() != null 548 && changingChanges.contains(info.getChange(change.getParent()))) { 549 // No-op if it will be covered by the changing parent window. 550 continue; 551 } 552 hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode()); 553 hasClosingWindow |= TransitionUtil.isClosingType(change.getMode()); 554 } 555 return hasOpeningWindow && hasClosingWindow; 556 } 557 558 /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */ prepareForJumpCut(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)559 private void prepareForJumpCut(@NonNull TransitionInfo info, 560 @NonNull SurfaceControl.Transaction startTransaction) { 561 for (TransitionInfo.Change change : info.getChanges()) { 562 final SurfaceControl leash = change.getLeash(); 563 if (change.getParent() != null) { 564 startTransaction.setPosition(leash, 565 change.getEndRelOffset().x, change.getEndRelOffset().y); 566 } else { 567 // Change leash has been reparented to the root if its parent is not in the 568 // transition. 569 // Because it is reparented to the root, the actual offset should be its relative 570 // position to the root instead. See Transitions#setupAnimHierarchy. 571 final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info); 572 startTransaction.setPosition(leash, 573 change.getEndAbsBounds().left - root.getOffset().x, 574 change.getEndAbsBounds().top - root.getOffset().y); 575 } 576 startTransaction.setWindowCrop(leash, 577 change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); 578 if (change.getMode() == TRANSIT_CLOSE) { 579 startTransaction.hide(leash); 580 } else { 581 startTransaction.show(leash); 582 startTransaction.setAlpha(leash, 1f); 583 } 584 } 585 } 586 587 /** To provide an {@link Animation} based on the transition infos. */ 588 private interface AnimationProvider { get(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect animationBounds)589 Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, 590 @NonNull Rect animationBounds); 591 } 592 } 593