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