Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f2296dcb authored by Chris Li's avatar Chris Li
Browse files

Fix ActivityEmbedding animation with new split in different layout

When launching an activity into a new TaskFragment with a different
split layout from the existing split, there can be many situations, such
as different split ratio or layout direction.

Use a jump cut for now since it is not a common use case, and it is hard
to cover all situations.

Bug: 241043533
Fix: 258126611
Test: verify with opening new activity to new split layout.
Change-Id: I6c96518b282567339fb890e7f4af7d9d82c98418
parent bac0f96f
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_CHANGING;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
@@ -253,6 +254,10 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
    @NonNull
    private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets) {
        if (shouldUseJumpCutForChangeAnimation(targets)) {
            return new ArrayList<>();
        }

        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
        for (RemoteAnimationTarget target : targets) {
            if (target.mode == MODE_CHANGING) {
@@ -282,4 +287,24 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
        }
        return adapters;
    }

    /**
     * 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 shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
        boolean hasOpeningWindow = false;
        boolean hasClosingWindow = false;
        for (RemoteAnimationTarget target : targets) {
            if (target.hasAnimatingParent) {
                continue;
            }
            hasOpeningWindow |= target.mode == MODE_OPENING;
            hasClosingWindow |= target.mode == MODE_CLOSING;
        }
        return hasOpeningWindow && hasClosingWindow;
    }
}
+99 −17
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
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;

@@ -112,15 +113,20 @@ class ActivityEmbeddingAnimationRunner {
            @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
        final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
                startTransaction);
        addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks,
                adapters);
        addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
        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());
            }
        final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setDuration(duration);
            animator.addUpdateListener((anim) -> {
                // Update all adapters in the same transaction.
                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -129,6 +135,8 @@ class ActivityEmbeddingAnimationRunner {
                }
                t.apply();
            });
        }
        animator.setDuration(duration);
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {}
@@ -292,6 +300,10 @@ class ActivityEmbeddingAnimationRunner {
    @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<>();

@@ -374,9 +386,11 @@ class ActivityEmbeddingAnimationRunner {
            }

            final Animation animation;
            if (change.getParent() != null
                    && handledChanges.contains(info.getChange(change.getParent()))) {
                // No-op if it will be covered by the changing parent window.
            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 (Transitions.isClosingType(change.getMode())) {
                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
@@ -421,6 +435,74 @@ class ActivityEmbeddingAnimationRunner {
                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 && Transitions.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 |= Transitions.isOpeningType(change.getMode());
            hasClosingWindow |= Transitions.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();
            startTransaction.setPosition(leash,
                    change.getEndRelOffset().x, change.getEndRelOffset().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,