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

Commit d2c6e463 authored by Charles Chen's avatar Charles Chen
Browse files

Add animation override support to AE

1. Add endParentSize to Change - It's necessary to calculate overlay Change transition.
2. Add support to obtain custom animation from Change's AnimationOptions
3. Respect the shouldShowBackdrop value if there's an override from
   AnimationOptions

Bug: 295805497
Test: atest WMShellUnitTests
Flag: com.android.window.flags.activity_embedding_overlay_presentation_flag
Flag: com.android.window.flags.move_animation_options_to_change

Change-Id: I0799947a1e468c426945f77ce8d969cfb81a0d58
parent bcc59ec1
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -663,6 +663,7 @@ public final class TransitionInfo implements Parcelable {
        private final Rect mStartAbsBounds = new Rect();
        private final Rect mEndAbsBounds = new Rect();
        private final Point mEndRelOffset = new Point();
        private final Point mEndParentSize = new Point();
        private ActivityManager.RunningTaskInfo mTaskInfo = null;
        private boolean mAllowEnterPip;
        private int mStartDisplayId = INVALID_DISPLAY;
@@ -697,6 +698,7 @@ public final class TransitionInfo implements Parcelable {
            mStartAbsBounds.readFromParcel(in);
            mEndAbsBounds.readFromParcel(in);
            mEndRelOffset.readFromParcel(in);
            mEndParentSize.readFromParcel(in);
            mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
            mAllowEnterPip = in.readBoolean();
            mStartDisplayId = in.readInt();
@@ -721,6 +723,7 @@ public final class TransitionInfo implements Parcelable {
            out.mStartAbsBounds.set(mStartAbsBounds);
            out.mEndAbsBounds.set(mEndAbsBounds);
            out.mEndRelOffset.set(mEndRelOffset);
            out.mEndParentSize.set(mEndParentSize);
            out.mTaskInfo = mTaskInfo;
            out.mAllowEnterPip = mAllowEnterPip;
            out.mStartDisplayId = mStartDisplayId;
@@ -780,6 +783,13 @@ public final class TransitionInfo implements Parcelable {
            mEndRelOffset.set(left, top);
        }

        /**
         * Sets the size of its parent container after the change.
         */
        public void setEndParentSize(int width, int height) {
            mEndParentSize.set(width, height);
        }

        /**
         * Sets the taskinfo of this container if this is a task. WARNING: this takes the
         * reference, so don't modify it afterwards.
@@ -916,6 +926,14 @@ public final class TransitionInfo implements Parcelable {
            return mEndRelOffset;
        }

        /**
         * Returns the size of parent container after the change.
         */
        @NonNull
        public Point getEndParentSize() {
            return mEndParentSize;
        }

        /** @return the leash or surface to animate for this container */
        @NonNull
        public SurfaceControl getLeash() {
@@ -1003,6 +1021,7 @@ public final class TransitionInfo implements Parcelable {
            mStartAbsBounds.writeToParcel(dest, flags);
            mEndAbsBounds.writeToParcel(dest, flags);
            mEndRelOffset.writeToParcel(dest, flags);
            mEndParentSize.writeToParcel(dest, flags);
            dest.writeTypedObject(mTaskInfo, flags);
            dest.writeBoolean(mAllowEnterPip);
            dest.writeInt(mStartDisplayId);
@@ -1055,6 +1074,9 @@ public final class TransitionInfo implements Parcelable {
            if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
                sb.append(" eo="); sb.append(mEndRelOffset);
            }
            if (!mEndParentSize.equals(0, 0)) {
                sb.append(" epz=").append(mEndParentSize);
            }
            sb.append(" d=");
            if (mStartDisplayId != mEndDisplayId) {
                sb.append(mStartDisplayId).append("->");
+59 −14
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
@@ -45,6 +46,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.shared.TransitionUtil;
@@ -398,7 +400,15 @@ class ActivityEmbeddingAnimationRunner {
        // This is because the TaskFragment surface/change won't contain the Activity's before its
        // reparent.
        Animation changeAnimation = null;
        Rect parentBounds = new Rect();
        final Rect parentBounds = new Rect();
        // We use a single boolean value to record the backdrop override because the override used
        // for overlay and we restrict to single overlay animation. We should fix the assumption
        // if we allow multiple overlay transitions.
        // The backdrop logic is mainly for animations of split animations. The backdrop should be
        // disabled if there is any open/close target in the same transition as the change target.
        // However, the overlay change animation usually contains one change target, and shows
        // backdrop unexpectedly.
        Boolean overrideShowBackdrop = null;
        for (TransitionInfo.Change change : info.getChanges()) {
            if (change.getMode() != TRANSIT_CHANGE
                    || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -421,17 +431,17 @@ class ActivityEmbeddingAnimationRunner {
                }
            }

            // 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());
            final TransitionInfo.AnimationOptions options = boundsAnimationChange
                    .getAnimationOptions();
            if (options != null) {
                final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions(
                        options, TRANSIT_CHANGE);
                if (overrideAnimation != null) {
                    overrideShowBackdrop = overrideAnimation.getShowBackdrop();
                }
            }

            calculateParentBounds(change, boundsAnimationChange, parentBounds);
            // 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,
@@ -466,7 +476,7 @@ class ActivityEmbeddingAnimationRunner {

        // 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;
        boolean shouldShowBackgroundColor = 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)) {
@@ -483,16 +493,18 @@ class ActivityEmbeddingAnimationRunner {
                animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
            } else if (TransitionUtil.isClosingType(change.getMode())) {
                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
                shouldShouldBackgroundColor = false;
                shouldShowBackgroundColor = false;
            } else {
                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
                shouldShouldBackgroundColor = false;
                shouldShowBackgroundColor = false;
            }
            adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change,
                    TransitionUtil.getRootFor(change, info)));
        }

        if (shouldShouldBackgroundColor && changeAnimation != null) {
        shouldShowBackgroundColor = overrideShowBackdrop != null
                ? overrideShowBackdrop : shouldShowBackgroundColor;
        if (shouldShowBackgroundColor && changeAnimation != null) {
            // Change animation may leave part of the screen empty. Show background color to cover
            // that.
            changeAnimation.setShowBackdrop(true);
@@ -501,6 +513,39 @@ class ActivityEmbeddingAnimationRunner {
        return adapters;
    }

    /**
     * Calculates parent bounds of the animation target by {@code change}.
     */
    @VisibleForTesting
    static void calculateParentBounds(@NonNull TransitionInfo.Change change,
              @NonNull TransitionInfo.Change boundsAnimationChange, @NonNull Rect outParentBounds) {
        if (Flags.activityEmbeddingOverlayPresentationFlag()) {
            final Point endParentSize = change.getEndParentSize();
            if (endParentSize.equals(0, 0)) {
                return;
            }
            final Point endRelPosition = change.getEndRelOffset();
            final Point endAbsPosition = new Point(change.getEndAbsBounds().left,
                    change.getEndAbsBounds().top);
            final Point parentEndAbsPosition = new Point(endAbsPosition.x - endRelPosition.x,
                    endAbsPosition.y - endRelPosition.y);
            outParentBounds.set(parentEndAbsPosition.x, parentEndAbsPosition.y,
                    parentEndAbsPosition.x + endParentSize.x,
                    parentEndAbsPosition.y + endParentSize.y);
        } else {
            // The TaskFragment may be enter/exit split, so we take the union of both as
            // the parent size.
            outParentBounds.union(boundsAnimationChange.getStartAbsBounds());
            outParentBounds.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.
                outParentBounds.union(change.getStartAbsBounds());
            }
        }
    }

    /**
     * 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
+30 −4
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.wm.shell.activityembedding;


import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID;

import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
@@ -27,6 +29,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
@@ -203,7 +207,7 @@ class ActivityEmbeddingAnimationSpec {
    Animation loadOpenAnimation(@NonNull TransitionInfo info,
            @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
        final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
        final Animation customAnimation = loadCustomAnimation(info, change, isEnter);
        final Animation customAnimation = loadCustomAnimation(info, change);
        final Animation animation;
        if (customAnimation != null) {
            animation = customAnimation;
@@ -230,7 +234,7 @@ class ActivityEmbeddingAnimationSpec {
    Animation loadCloseAnimation(@NonNull TransitionInfo info,
            @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
        final boolean isEnter = TransitionUtil.isOpeningType(change.getMode());
        final Animation customAnimation = loadCustomAnimation(info, change, isEnter);
        final Animation customAnimation = loadCustomAnimation(info, change);
        final Animation animation;
        if (customAnimation != null) {
            animation = customAnimation;
@@ -263,18 +267,40 @@ class ActivityEmbeddingAnimationSpec {

    @Nullable
    private Animation loadCustomAnimation(@NonNull TransitionInfo info,
            @NonNull TransitionInfo.Change change, boolean isEnter) {
            @NonNull TransitionInfo.Change change) {
        final TransitionInfo.AnimationOptions options;
        if (Flags.moveAnimationOptionsToChange()) {
            options = change.getAnimationOptions();
        } else {
            options = info.getAnimationOptions();
        }
        return loadCustomAnimationFromOptions(options, change.getMode());
    }

    @Nullable
    Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options,
             @WindowManager.TransitionType int mode) {
        if (options == null || options.getType() != ANIM_CUSTOM) {
            return null;
        }
        final int resId;
        if (TransitionUtil.isOpeningType(mode)) {
            resId = options.getEnterResId();
        } else if (TransitionUtil.isClosingType(mode)) {
            resId = options.getExitResId();
        } else if (mode == TRANSIT_CHANGE) {
            resId = options.getChangeResId();
        } else {
            Log.w(TAG, "Unknown transit type:" + mode);
            resId = DEFAULT_ANIMATION_RESOURCES_ID;
        }
        // Use the default animation if the resources ID is not specified.
        if (resId == DEFAULT_ANIMATION_RESOURCES_ID) {
            return null;
        }

        final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(),
                isEnter ? options.getEnterResId() : options.getExitResId());
                resId);
        if (anim != null) {
            return anim;
        }
+131 −2
Original line number Diff line number Diff line
@@ -20,9 +20,11 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;

import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds;
import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -32,6 +34,9 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.animation.Animator;
import android.annotation.NonNull;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -130,7 +135,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
    @Test
    public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() {
        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
                .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
                .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
                .build();
        info.setAnimationOptions(TransitionInfo.AnimationOptions
                .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
@@ -148,7 +153,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
    @Test
    public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() {
        final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
                .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
                .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
                .build();
        info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions
                .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
@@ -161,4 +166,128 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
        // An invalid custom animation is equivalent to jump-cut.
        assertEquals(0, animator.getDuration());
    }

    @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
    @Test
    public void testCalculateParentBounds_flagDisabled() {
        final Rect parentBounds = new Rect(0, 0, 2000, 2000);
        final Rect primaryBounds = new Rect();
        final Rect secondaryBounds = new Rect();
        parentBounds.splitVertically(primaryBounds, secondaryBounds);

        final TransitionInfo.Change change = createChange(0 /* flags */);
        change.setStartAbsBounds(secondaryBounds);

        final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */);
        boundsAnimationChange.setStartAbsBounds(primaryBounds);
        boundsAnimationChange.setEndAbsBounds(primaryBounds);
        final Rect actualParentBounds = new Rect();

        calculateParentBounds(change, boundsAnimationChange, actualParentBounds);

        assertEquals(parentBounds, actualParentBounds);

        actualParentBounds.setEmpty();

        boundsAnimationChange.setStartAbsBounds(secondaryBounds);
        boundsAnimationChange.setEndAbsBounds(primaryBounds);

        calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds);

        assertEquals(parentBounds, actualParentBounds);
    }

    // TODO(b/243518738): Rewrite with TestParameter
    @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG)
    @Test
    public void testCalculateParentBounds_flagEnabled() {
        TransitionInfo.Change change;
        final TransitionInfo.Change stubChange = createChange(0 /* flags */);
        final Rect actualParentBounds = new Rect();
        Rect parentBounds = new Rect(0, 0, 2000, 2000);
        Rect endAbsBounds = new Rect(0, 0, 2000, 2000);
        change = prepareChangeForParentBoundsCalculationTest(
                new Point(0, 0) /* endRelOffset */,
                endAbsBounds,
                new Point() /* endParentSize */
        );

        calculateParentBounds(change, stubChange, actualParentBounds);

        assertTrue("Parent bounds must be empty because end parent size is not set.",
                actualParentBounds.isEmpty());

        String testString = "Parent start with (0, 0)";
        change = prepareChangeForParentBoundsCalculationTest(
                new Point(endAbsBounds.left - parentBounds.left,
                        endAbsBounds.top - parentBounds.top),
                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));

        calculateParentBounds(change, stubChange, actualParentBounds);

        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
                actualParentBounds);

        testString = "Container not start with (0, 0)";
        parentBounds = new Rect(0, 0, 2000, 2000);
        endAbsBounds = new Rect(1000, 500, 2000, 1500);
        change = prepareChangeForParentBoundsCalculationTest(
                new Point(endAbsBounds.left - parentBounds.left,
                        endAbsBounds.top - parentBounds.top),
                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));

        calculateParentBounds(change, stubChange, actualParentBounds);

        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
                actualParentBounds);

        testString = "Parent container on the right";
        parentBounds = new Rect(1000, 0, 2000, 2000);
        endAbsBounds = new Rect(1000, 500, 1500, 1500);
        change = prepareChangeForParentBoundsCalculationTest(
                new Point(endAbsBounds.left - parentBounds.left,
                        endAbsBounds.top - parentBounds.top),
                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));

        calculateParentBounds(change, stubChange, actualParentBounds);

        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
                actualParentBounds);

        testString = "Parent container on the bottom";
        parentBounds = new Rect(0, 1000, 2000, 2000);
        endAbsBounds = new Rect(500, 1500, 1500, 2000);
        change = prepareChangeForParentBoundsCalculationTest(
                new Point(endAbsBounds.left - parentBounds.left,
                        endAbsBounds.top - parentBounds.top),
                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));

        calculateParentBounds(change, stubChange, actualParentBounds);

        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
                actualParentBounds);

        testString = "Parent container in the middle";
        parentBounds = new Rect(500, 500, 1500, 1500);
        endAbsBounds = new Rect(1000, 500, 1500, 1000);
        change = prepareChangeForParentBoundsCalculationTest(
                new Point(endAbsBounds.left - parentBounds.left,
                        endAbsBounds.top - parentBounds.top),
                endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));

        calculateParentBounds(change, stubChange, actualParentBounds);

        assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds,
                actualParentBounds);
    }

    @NonNull
    private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest(
            @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) {
        final TransitionInfo.Change change = createChange(0 /* flags */);
        change.setEndRelOffset(endRelOffset.x, endRelOffset.y);
        change.setEndAbsBounds(endAbsBounds);
        change.setEndParentSize(endParentSize.x, endParentSize.y);
        return change;
    }
}
+19 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.wm.shell.activityembedding;

import static android.view.WindowManager.TRANSIT_NONE;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;

@@ -31,6 +32,7 @@ import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;

@@ -82,11 +84,27 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
        spyOn(mFinishCallback);
    }

    /** Creates a mock {@link TransitionInfo.Change}. */
    /**
     * Creates a mock {@link TransitionInfo.Change}.
     *
     * @param flags the {@link TransitionInfo.ChangeFlags} of the change
     */
    static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) {
        return createChange(flags, TRANSIT_NONE);
    }

    /**
     * Creates a mock {@link TransitionInfo.Change}.
     *
     * @param flags the {@link TransitionInfo.ChangeFlags} of the change
     * @param mode the transition mode of the change
     */
    static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags,
            @WindowManager.TransitionType int mode) {
        TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class),
                mock(SurfaceControl.class));
        c.setFlags(flags);
        c.setMode(mode);
        return c;
    }

Loading