| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.wm.shell.activityembedding; |
| |
| import static android.view.WindowManager.TRANSIT_CHANGE; |
| import static android.view.WindowManager.TRANSIT_CLOSE; |
| import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; |
| import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; |
| |
| import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation; |
| import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; |
| import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; |
| import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; |
| |
| import android.animation.Animator; |
| import android.animation.ValueAnimator; |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.os.IBinder; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.view.Choreographer; |
| import android.view.SurfaceControl; |
| import android.view.animation.Animation; |
| import android.window.TransitionInfo; |
| import android.window.WindowContainerToken; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter; |
| import com.android.wm.shell.common.ScreenshotUtils; |
| import com.android.wm.shell.util.TransitionUtil; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| |
| /** To run the ActivityEmbedding animations. */ |
| class ActivityEmbeddingAnimationRunner { |
| |
| private static final String TAG = "ActivityEmbeddingAnimR"; |
| |
| private final ActivityEmbeddingController mController; |
| @VisibleForTesting |
| final ActivityEmbeddingAnimationSpec mAnimationSpec; |
| |
| @Nullable |
| private Animator mActiveAnimator; |
| |
| ActivityEmbeddingAnimationRunner(@NonNull Context context, |
| @NonNull ActivityEmbeddingController controller) { |
| mController = controller; |
| mAnimationSpec = new ActivityEmbeddingAnimationSpec(context); |
| } |
| |
| /** Creates and starts animation for ActivityEmbedding transition. */ |
| void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, |
| @NonNull SurfaceControl.Transaction startTransaction, |
| @NonNull SurfaceControl.Transaction finishTransaction) { |
| // There may be some surface change that we want to apply after the start transaction is |
| // applied to make sure the surface is ready. |
| final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = |
| new ArrayList<>(); |
| final Animator animator = createAnimator(info, startTransaction, |
| finishTransaction, |
| () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks); |
| mActiveAnimator = animator; |
| |
| // Start the animation. |
| if (!postStartTransactionCallbacks.isEmpty()) { |
| // postStartTransactionCallbacks require that the start transaction is already |
| // applied to run otherwise they may result in flickers and UI inconsistencies. |
| startTransaction.apply(true /* sync */); |
| |
| // Run tasks that require startTransaction to already be applied |
| final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); |
| for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback : |
| postStartTransactionCallbacks) { |
| postStartTransactionCallback.accept(t); |
| } |
| t.apply(); |
| animator.start(); |
| } else { |
| startTransaction.apply(); |
| animator.start(); |
| } |
| } |
| |
| void cancelAnimationFromMerge() { |
| if (mActiveAnimator == null) { |
| Log.e(TAG, |
| "No active ActivityEmbedding animator running but mergeAnimation is " |
| + "trying to cancel one." |
| ); |
| return; |
| } |
| mActiveAnimator.end(); |
| } |
| |
| /** |
| * Sets transition animation scale settings value. |
| * @param scale The setting value of transition animation scale. |
| */ |
| void setAnimScaleSetting(float scale) { |
| mAnimationSpec.setAnimScaleSetting(scale); |
| } |
| |
| /** Creates the animator for the given {@link TransitionInfo}. */ |
| @VisibleForTesting |
| @NonNull |
| Animator createAnimator(@NonNull TransitionInfo info, |
| @NonNull SurfaceControl.Transaction startTransaction, |
| @NonNull SurfaceControl.Transaction finishTransaction, |
| @NonNull Runnable animationFinishCallback, |
| @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) { |
| final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info, |
| startTransaction); |
| final ValueAnimator animator = ValueAnimator.ofFloat(0, 1); |
| long duration = 0; |
| if (adapters.isEmpty()) { |
| // Jump cut |
| // No need to modify the animator, but to update the startTransaction with the changes' |
| // ending states. |
| prepareForJumpCut(info, startTransaction); |
| } else { |
| addEdgeExtensionIfNeeded(startTransaction, finishTransaction, |
| postStartTransactionCallbacks, adapters); |
| addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters); |
| for (ActivityEmbeddingAnimationAdapter adapter : adapters) { |
| duration = Math.max(duration, adapter.getDurationHint()); |
| } |
| animator.addUpdateListener((anim) -> { |
| // Update all adapters in the same transaction. |
| final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); |
| t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); |
| for (ActivityEmbeddingAnimationAdapter adapter : adapters) { |
| adapter.onAnimationUpdate(t, animator.getCurrentPlayTime()); |
| } |
| t.apply(); |
| }); |
| prepareForFirstFrame(startTransaction, adapters); |
| } |
| animator.setDuration(duration); |
| animator.addListener(new Animator.AnimatorListener() { |
| @Override |
| public void onAnimationStart(Animator animation) {} |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); |
| for (ActivityEmbeddingAnimationAdapter adapter : adapters) { |
| adapter.onAnimationEnd(t); |
| } |
| t.apply(); |
| mActiveAnimator = null; |
| animationFinishCallback.run(); |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animation) {} |
| }); |
| return animator; |
| } |
| |
| /** |
| * Creates list of {@link ActivityEmbeddingAnimationAdapter} to handle animations on all window |
| * changes. |
| */ |
| @NonNull |
| private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( |
| @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { |
| boolean isChangeTransition = false; |
| for (TransitionInfo.Change change : info.getChanges()) { |
| if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) { |
| // Skip the animation if the windows are behind an app starting window. |
| return new ArrayList<>(); |
| } |
| if (!isChangeTransition && change.getMode() == TRANSIT_CHANGE |
| && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { |
| isChangeTransition = true; |
| } |
| } |
| if (isChangeTransition) { |
| return createChangeAnimationAdapters(info, startTransaction); |
| } |
| if (TransitionUtil.isClosingType(info.getType())) { |
| return createCloseAnimationAdapters(info, startTransaction); |
| } |
| return createOpenAnimationAdapters(info, startTransaction); |
| } |
| |
| @NonNull |
| private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters( |
| @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { |
| return createOpenCloseAnimationAdapters(info, true /* isOpening */, |
| mAnimationSpec::loadOpenAnimation, startTransaction); |
| } |
| |
| @NonNull |
| private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters( |
| @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { |
| return createOpenCloseAnimationAdapters(info, false /* isOpening */, |
| mAnimationSpec::loadCloseAnimation, startTransaction); |
| } |
| |
| /** |
| * Creates {@link ActivityEmbeddingAnimationAdapter} for OPEN and CLOSE types of transition. |
| * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type. |
| */ |
| @NonNull |
| private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters( |
| @NonNull TransitionInfo info, boolean isOpening, |
| @NonNull AnimationProvider animationProvider, |
| @NonNull SurfaceControl.Transaction startTransaction) { |
| // We need to know if the change window is only a partial of the whole animation screen. |
| // If so, we will need to adjust it to make the whole animation screen looks like one. |
| final List<TransitionInfo.Change> openingChanges = new ArrayList<>(); |
| final List<TransitionInfo.Change> closingChanges = new ArrayList<>(); |
| final Rect openingWholeScreenBounds = new Rect(); |
| final Rect closingWholeScreenBounds = new Rect(); |
| for (TransitionInfo.Change change : info.getChanges()) { |
| if (TransitionUtil.isOpeningType(change.getMode())) { |
| openingChanges.add(change); |
| openingWholeScreenBounds.union(change.getEndAbsBounds()); |
| } else { |
| closingChanges.add(change); |
| // Also union with the start bounds because the closing transition may be shrunk. |
| closingWholeScreenBounds.union(change.getStartAbsBounds()); |
| closingWholeScreenBounds.union(change.getEndAbsBounds()); |
| } |
| } |
| |
| // For OPEN transition, open windows should be above close windows. |
| // For CLOSE transition, open windows should be below close windows. |
| int offsetLayer = TYPE_LAYER_OFFSET; |
| final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); |
| for (TransitionInfo.Change change : openingChanges) { |
| final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( |
| info, change, animationProvider, openingWholeScreenBounds); |
| if (isOpening) { |
| adapter.overrideLayer(offsetLayer++); |
| } |
| adapters.add(adapter); |
| } |
| for (TransitionInfo.Change change : closingChanges) { |
| if (shouldUseSnapshotAnimationForClosingChange(change)) { |
| SurfaceControl screenshot = getOrCreateScreenshot(change, change, startTransaction); |
| if (screenshot != null) { |
| final SnapshotAdapter snapshotAdapter = new SnapshotAdapter( |
| createShowSnapshotForClosingAnimation(), change, screenshot, |
| TransitionUtil.getRootFor(change, info)); |
| if (!isOpening) { |
| snapshotAdapter.overrideLayer(offsetLayer++); |
| } |
| adapters.add(snapshotAdapter); |
| } |
| } |
| final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( |
| info, change, animationProvider, closingWholeScreenBounds); |
| if (!isOpening) { |
| adapter.overrideLayer(offsetLayer++); |
| } |
| adapters.add(adapter); |
| } |
| return adapters; |
| } |
| |
| /** |
| * Returns whether we should use snapshot animation for the closing change. |
| * It's usually because the end bounds of the closing change are shrunk, which leaves a black |
| * area in the transition. |
| */ |
| static boolean shouldUseSnapshotAnimationForClosingChange( |
| @NonNull TransitionInfo.Change closingChange) { |
| // Only check closing type because we only take screenshot for closing bounds-changing |
| // changes. |
| if (!TransitionUtil.isClosingType(closingChange.getMode())) { |
| return false; |
| } |
| // Don't need to take screenshot if there's no bounds change. |
| return !closingChange.getStartAbsBounds().equals(closingChange.getEndAbsBounds()); |
| } |
| |
| /** Sets the first frame to the {@code startTransaction} to avoid any flicker on start. */ |
| private void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction, |
| @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { |
| startTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); |
| for (ActivityEmbeddingAnimationAdapter adapter : adapters) { |
| adapter.prepareForFirstFrame(startTransaction); |
| } |
| } |
| |
| /** Adds edge extension to the surfaces that have such an animation property. */ |
| private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction, |
| @NonNull SurfaceControl.Transaction finishTransaction, |
| @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks, |
| @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { |
| for (ActivityEmbeddingAnimationAdapter adapter : adapters) { |
| final Animation animation = adapter.mAnimation; |
| if (!animation.hasExtension()) { |
| continue; |
| } |
| final TransitionInfo.Change change = adapter.mChange; |
| if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) { |
| // Need to screenshot after startTransaction is applied otherwise activity |
| // may not be visible or ready yet. |
| postStartTransactionCallbacks.add( |
| t -> edgeExtendWindow(change, animation, t, finishTransaction)); |
| } else { |
| // Can screenshot now (before startTransaction is applied) |
| edgeExtendWindow(change, animation, startTransaction, finishTransaction); |
| } |
| } |
| } |
| |
| /** Adds background color to the transition if any animation has such a property. */ |
| private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info, |
| @NonNull SurfaceControl.Transaction startTransaction, |
| @NonNull SurfaceControl.Transaction finishTransaction, |
| @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) { |
| for (ActivityEmbeddingAnimationAdapter adapter : adapters) { |
| final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange, |
| adapter.mAnimation, 0 /* defaultColor */); |
| if (backgroundColor != 0) { |
| // We only need to show one color. |
| addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction, |
| finishTransaction); |
| return; |
| } |
| } |
| } |
| |
| @NonNull |
| private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter( |
| @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, |
| @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) { |
| final Animation animation = animationProvider.get(info, change, wholeAnimationBounds); |
| return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(), |
| wholeAnimationBounds, TransitionUtil.getRootFor(change, info)); |
| } |
| |
| @NonNull |
| private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters( |
| @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { |
| if (shouldUseJumpCutForChangeTransition(info)) { |
| return new ArrayList<>(); |
| } |
| |
| final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); |
| final Set<TransitionInfo.Change> handledChanges = new ArraySet<>(); |
| |
| // For the first iteration, we prepare the animation for the change type windows. This is |
| // needed because there may be window that is reparented while resizing. In such case, we |
| // will do the following: |
| // 1. Capture a screenshot from the Activity surface. |
| // 2. Attach the screenshot surface to the top of TaskFragment (Activity's parent) surface. |
| // 3. Animate the TaskFragment using Activity Change info (start/end bounds). |
| // This is because the TaskFragment surface/change won't contain the Activity's before its |
| // reparent. |
| Animation changeAnimation = null; |
| Rect parentBounds = new Rect(); |
| for (TransitionInfo.Change change : info.getChanges()) { |
| if (change.getMode() != TRANSIT_CHANGE |
| || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { |
| continue; |
| } |
| |
| // This is the window with bounds change. |
| handledChanges.add(change); |
| final WindowContainerToken parentToken = change.getParent(); |
| TransitionInfo.Change boundsAnimationChange = change; |
| if (parentToken != null) { |
| // When the parent window is also included in the transition as an opening window, |
| // we would like to animate the parent window instead. |
| final TransitionInfo.Change parentChange = info.getChange(parentToken); |
| if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) { |
| // We won't create a separate animation for the parent, but to animate the |
| // parent for the child resizing. |
| handledChanges.add(parentChange); |
| boundsAnimationChange = parentChange; |
| } |
| } |
| |
| // The TaskFragment may be enter/exit split, so we take the union of both as the parent |
| // size. |
| parentBounds.union(boundsAnimationChange.getStartAbsBounds()); |
| parentBounds.union(boundsAnimationChange.getEndAbsBounds()); |
| if (boundsAnimationChange != change) { |
| // Union the change starting bounds in case the activity is resized and reparented |
| // to a TaskFragment. In that case, the TaskFragment may not cover the activity's |
| // starting bounds. |
| parentBounds.union(change.getStartAbsBounds()); |
| } |
| |
| // There are two animations in the array. The first one is for the start leash |
| // (snapshot), and the second one is for the end leash (TaskFragment). |
| final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, |
| parentBounds); |
| // Keep track as we might need to add background color for the animation. |
| // Although there may be multiple change animation, record one of them is sufficient |
| // because the background color will be added to the root leash for the whole animation. |
| changeAnimation = animations[1]; |
| |
| // Create a screenshot based on change, but attach it to the top of the |
| // boundsAnimationChange. |
| final SurfaceControl screenshotLeash = getOrCreateScreenshot(change, |
| boundsAnimationChange, startTransaction); |
| final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info); |
| if (screenshotLeash != null) { |
| // Adapter for the starting screenshot leash. |
| // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd |
| adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( |
| animations[0], change, screenshotLeash, root)); |
| } else { |
| Log.e(TAG, "Failed to take screenshot for change=" + change); |
| } |
| // Adapter for the ending bounds changed leash. |
| adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( |
| animations[1], boundsAnimationChange, root)); |
| } |
| |
| if (parentBounds.isEmpty()) { |
| throw new IllegalStateException( |
| "There should be at least one changing window to play the change animation"); |
| } |
| |
| // If there is no corresponding open/close window with the change, we should show background |
| // color to cover the empty part of the screen. |
| boolean shouldShouldBackgroundColor = true; |
| // Handle the other windows that don't have bounds change in the same transition. |
| for (TransitionInfo.Change change : info.getChanges()) { |
| if (handledChanges.contains(change)) { |
| // Skip windows that we have already handled in the previous iteration. |
| continue; |
| } |
| |
| final Animation animation; |
| if ((change.getParent() != null |
| && handledChanges.contains(info.getChange(change.getParent()))) |
| || change.getMode() == TRANSIT_CHANGE) { |
| // No-op if it will be covered by the changing parent window, or it is a changing |
| // window without bounds change. |
| animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); |
| } else if (TransitionUtil.isClosingType(change.getMode())) { |
| animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); |
| shouldShouldBackgroundColor = false; |
| } else { |
| animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); |
| shouldShouldBackgroundColor = false; |
| } |
| adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, |
| TransitionUtil.getRootFor(change, info))); |
| } |
| |
| if (shouldShouldBackgroundColor && changeAnimation != null) { |
| // Change animation may leave part of the screen empty. Show background color to cover |
| // that. |
| changeAnimation.setShowBackdrop(true); |
| } |
| |
| return adapters; |
| } |
| |
| /** |
| * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. |
| * The screenshot leash should be attached to the {@code animationChange} surface which we will |
| * animate later. |
| */ |
| @Nullable |
| private SurfaceControl getOrCreateScreenshot(@NonNull TransitionInfo.Change screenshotChange, |
| @NonNull TransitionInfo.Change animationChange, |
| @NonNull SurfaceControl.Transaction t) { |
| final SurfaceControl screenshotLeash = screenshotChange.getSnapshot(); |
| if (screenshotLeash != null) { |
| // If WM Core has already taken a screenshot, make sure it is reparented to the |
| // animation leash. |
| t.reparent(screenshotLeash, animationChange.getLeash()); |
| return screenshotLeash; |
| } |
| |
| // If WM Core hasn't taken a screenshot, take a screenshot now. |
| final Rect cropBounds = new Rect(screenshotChange.getStartAbsBounds()); |
| cropBounds.offsetTo(0, 0); |
| return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(), |
| animationChange.getLeash(), cropBounds, Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Whether we should use jump cut for the change transition. |
| * This normally happens when opening a new secondary with the existing primary using a |
| * different split layout. This can be complicated, like from horizontal to vertical split with |
| * new split pairs. |
| * Uses a jump cut animation to simplify. |
| */ |
| private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) { |
| // There can be reparenting of changing Activity to new open TaskFragment, so we need to |
| // exclude both in the first iteration. |
| final List<TransitionInfo.Change> changingChanges = new ArrayList<>(); |
| for (TransitionInfo.Change change : info.getChanges()) { |
| if (change.getMode() != TRANSIT_CHANGE |
| || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { |
| continue; |
| } |
| changingChanges.add(change); |
| final WindowContainerToken parentToken = change.getParent(); |
| if (parentToken != null) { |
| // When the parent window is also included in the transition as an opening window, |
| // we would like to animate the parent window instead. |
| final TransitionInfo.Change parentChange = info.getChange(parentToken); |
| if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) { |
| changingChanges.add(parentChange); |
| } |
| } |
| } |
| if (changingChanges.isEmpty()) { |
| // No changing target found. |
| return true; |
| } |
| |
| // Check if the transition contains both opening and closing windows. |
| boolean hasOpeningWindow = false; |
| boolean hasClosingWindow = false; |
| for (TransitionInfo.Change change : info.getChanges()) { |
| if (changingChanges.contains(change)) { |
| continue; |
| } |
| if (change.getParent() != null |
| && changingChanges.contains(info.getChange(change.getParent()))) { |
| // No-op if it will be covered by the changing parent window. |
| continue; |
| } |
| hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode()); |
| hasClosingWindow |= TransitionUtil.isClosingType(change.getMode()); |
| } |
| return hasOpeningWindow && hasClosingWindow; |
| } |
| |
| /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */ |
| private void prepareForJumpCut(@NonNull TransitionInfo info, |
| @NonNull SurfaceControl.Transaction startTransaction) { |
| for (TransitionInfo.Change change : info.getChanges()) { |
| final SurfaceControl leash = change.getLeash(); |
| if (change.getParent() != null) { |
| startTransaction.setPosition(leash, |
| change.getEndRelOffset().x, change.getEndRelOffset().y); |
| } else { |
| // Change leash has been reparented to the root if its parent is not in the |
| // transition. |
| // Because it is reparented to the root, the actual offset should be its relative |
| // position to the root instead. See Transitions#setupAnimHierarchy. |
| final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info); |
| startTransaction.setPosition(leash, |
| change.getEndAbsBounds().left - root.getOffset().x, |
| change.getEndAbsBounds().top - root.getOffset().y); |
| } |
| startTransaction.setWindowCrop(leash, |
| change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); |
| if (change.getMode() == TRANSIT_CLOSE) { |
| startTransaction.hide(leash); |
| } else { |
| startTransaction.show(leash); |
| startTransaction.setAlpha(leash, 1f); |
| } |
| } |
| } |
| |
| /** To provide an {@link Animation} based on the transition infos. */ |
| private interface AnimationProvider { |
| Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, |
| @NonNull Rect animationBounds); |
| } |
| } |