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

Commit 850d69d3 authored by wilsonshih's avatar wilsonshih
Browse files

Play seekable animation for customize activity transition API.(2/N)

Add supporting for Activity#overrideActivityTransition.
The CustomizeActivityAnimation will load both enter and exit animation
for the close activity transition. And it is only valid if the exit
animation has set and loaded success. If the entering animation has not
set(i.e. 0), there will load the default entering animation for it.
Also note that if both overrideActivityTransition and windowAnimations
has set, system should prior to load the animation set from
overrideActivityTransition.

Bug: 259427810
Test: atest ActivityTransitionTests BackNavigationControllerTests\
CustomizeActivityAnimationTest

Change-Id: Id182dc8f93f55a256de68c1fb6cc91fd08450e3e
parent 25af92cd
Loading
Loading
Loading
Loading
+48 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.window;

import android.annotation.AnimRes;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -245,6 +247,9 @@ public final class BackNavigationInfo implements Parcelable {
    public static final class CustomAnimationInfo implements Parcelable {
        private final String mPackageName;
        private int mWindowAnimations;
        @AnimRes private int mCustomExitAnim;
        @AnimRes private int mCustomEnterAnim;
        @ColorInt private int mCustomBackground;

        /**
         * The package name of the windowAnimations.
@@ -261,6 +266,27 @@ public final class BackNavigationInfo implements Parcelable {
            return mWindowAnimations;
        }

        /**
         * The exit animation resource Id of customize activity transition.
         */
        public int getCustomExitAnim() {
            return mCustomExitAnim;
        }

        /**
         * The entering animation resource Id of customize activity transition.
         */
        public int getCustomEnterAnim() {
            return mCustomEnterAnim;
        }

        /**
         * The background color of customize activity transition.
         */
        public int getCustomBackground() {
            return mCustomBackground;
        }

        public CustomAnimationInfo(@NonNull String packageName) {
            this.mPackageName = packageName;
        }
@@ -274,11 +300,17 @@ public final class BackNavigationInfo implements Parcelable {
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeString8(mPackageName);
            dest.writeInt(mWindowAnimations);
            dest.writeInt(mCustomEnterAnim);
            dest.writeInt(mCustomExitAnim);
            dest.writeInt(mCustomBackground);
        }

        private CustomAnimationInfo(@NonNull Parcel in) {
            mPackageName = in.readString8();
            mWindowAnimations = in.readInt();
            mCustomEnterAnim = in.readInt();
            mCustomExitAnim = in.readInt();
            mCustomBackground = in.readInt();
        }

        @Override
@@ -349,10 +381,25 @@ public final class BackNavigationInfo implements Parcelable {
         * Set windowAnimations for customize animation.
         */
        public Builder setWindowAnimations(String packageName, int windowAnimations) {
            if (mCustomAnimationInfo == null) {
                mCustomAnimationInfo = new CustomAnimationInfo(packageName);
            }
            mCustomAnimationInfo.mWindowAnimations = windowAnimations;
            return this;
        }
        /**
         * Set resources ids for customize activity animation.
         */
        public Builder setCustomAnimation(String packageName, @AnimRes int enterResId,
                @AnimRes int exitResId, @ColorInt int backgroundColor) {
            if (mCustomAnimationInfo == null) {
                mCustomAnimationInfo = new CustomAnimationInfo(packageName);
            }
            mCustomAnimationInfo.mCustomExitAnim = exitResId;
            mCustomAnimationInfo.mCustomEnterAnim = enterResId;
            mCustomAnimationInfo.mCustomBackground = backgroundColor;
            return this;
        }

        /**
         * Builds and returns an instance of {@link BackNavigationInfo}
+78 −20
Original line number Diff line number Diff line
@@ -25,7 +25,10 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.FloatProperty;
@@ -34,6 +37,7 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
@@ -43,6 +47,7 @@ import android.window.BackNavigationInfo;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;

import com.android.internal.R;
import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
@@ -78,6 +83,7 @@ class CustomizeActivityAnimation {
    final CustomAnimationLoader mCustomAnimationLoader;
    private Animation mEnterAnimation;
    private Animation mCloseAnimation;
    private int mNextBackgroundColor;
    final Transformation mTransformation = new Transformation();

    private final Choreographer mChoreographer;
@@ -144,8 +150,9 @@ class CustomizeActivityAnimation {

        // Draw background with task background color.
        if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
            mBackground.ensureBackground(
                    mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
            mBackground.ensureBackground(mNextBackgroundColor == Color.TRANSPARENT
                    ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor()
                    : mNextBackgroundColor, mTransaction);
        }
    }

@@ -191,6 +198,7 @@ class CustomizeActivityAnimation {
        mTransaction.apply();
        mTransformation.clear();
        mLatestProgress = 0;
        mNextBackgroundColor = Color.TRANSPARENT;
        if (mFinishCallback != null) {
            try {
                mFinishCallback.onAnimationFinished();
@@ -252,11 +260,11 @@ class CustomizeActivityAnimation {
     * Load customize animation before animation start.
     */
    boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
        mCloseAnimation = mCustomAnimationLoader.load(
                animationInfo, false /* enterAnimation */);
        if (mCloseAnimation != null) {
            mEnterAnimation = mCustomAnimationLoader.load(
                    animationInfo, true /* enterAnimation */);
        final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
        if (result != null) {
            mCloseAnimation = result.mCloseAnimation;
            mEnterAnimation = result.mEnterAnimation;
            mNextBackgroundColor = result.mBackgroundColor;
            return true;
        }
        return false;
@@ -318,35 +326,79 @@ class CustomizeActivityAnimation {
        }
    }


    static final class AnimationLoadResult {
        Animation mCloseAnimation;
        Animation mEnterAnimation;
        int mBackgroundColor;
    }

    /**
     * Helper class to load custom animation.
     */
    static class CustomAnimationLoader {
        private final TransitionAnimation mTransitionAnimation;
        final TransitionAnimation mTransitionAnimation;

        CustomAnimationLoader(Context context) {
            mTransitionAnimation = new TransitionAnimation(
                    context, false /* debug */, "CustomizeBackAnimation");
        }

        Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
                boolean enterAnimation) {
            final String packageName = animationInfo.getPackageName();
            if (packageName.isEmpty()) {
        /**
         * Load both enter and exit animation for the close activity transition.
         * Note that the result is only valid if the exit animation has set and loaded success.
         * If the entering animation has not set(i.e. 0), here will load the default entering
         * animation for it.
         *
         * @param animationInfo The information of customize animation, which can be set from
         * {@link Activity#overrideActivityTransition} and/or
         * {@link LayoutParams#windowAnimations}
         */
        AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) {
            if (animationInfo.getPackageName().isEmpty()) {
                return null;
            }
            final int windowAnimations = animationInfo.getWindowAnimations();
            if (windowAnimations == 0) {
            final Animation close = loadAnimation(animationInfo, false);
            if (close == null) {
                return null;
            }
            final int attrs = enterAnimation
                    ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
                    : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
            Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations,
                    attrs, false /* translucent */);
            final Animation open = loadAnimation(animationInfo, true);
            AnimationLoadResult result = new AnimationLoadResult();
            result.mCloseAnimation = close;
            result.mEnterAnimation = open;
            result.mBackgroundColor = animationInfo.getCustomBackground();
            return result;
        }

        /**
         * Load enter or exit animation from CustomAnimationInfo
         * @param animationInfo The information for customize animation.
         * @param enterAnimation true when load for enter animation, false for exit animation.
         * @return Loaded animation.
         */
        @Nullable
        Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
                boolean enterAnimation) {
            Animation a = null;
            // Activity#overrideActivityTransition has higher priority than windowAnimations
            // Try to get animation from Activity#overrideActivityTransition
            if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0)
                    || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) {
                a = mTransitionAnimation.loadAppTransitionAnimation(
                        animationInfo.getPackageName(),
                        enterAnimation ? animationInfo.getCustomEnterAnim()
                                : animationInfo.getCustomExitAnim());
            } else if (animationInfo.getWindowAnimations() != 0) {
                // try to get animation from LayoutParams#windowAnimations
                a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(),
                        animationInfo.getWindowAnimations(), enterAnimation
                                ? R.styleable.WindowAnimation_activityCloseEnterAnimation
                                : R.styleable.WindowAnimation_activityCloseExitAnimation,
                        false /* translucent */);
            }
            // Only allow to load default animation for opening target.
            if (a == null && enterAnimation) {
                a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */);
                a = loadDefaultOpenAnimation();
            }
            if (a != null) {
                ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
@@ -355,5 +407,11 @@ class CustomizeActivityAnimation {
            }
            return a;
        }

        private Animation loadDefaultOpenAnimation() {
            return mTransitionAnimation.loadDefaultAnimationAttr(
                    R.styleable.WindowAnimation_activityCloseEnterAnimation,
                    false /* translucent */);
        }
    }
}
+82 −5
Original line number Diff line number Diff line
@@ -18,14 +18,20 @@ package com.android.wm.shell.back;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.WindowConfiguration;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -69,11 +75,7 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
                mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
                mock(Choreographer.class));
        spyOn(mCustomizeActivityAnimation);
        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
                .load(any(), eq(false));
        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
                .load(any(), eq(true));
        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation);
    }

    RemoteAnimationTarget createAnimationTarget(boolean open) {
@@ -87,6 +89,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {

    @Test
    public void receiveFinishAfterInvoke() throws InterruptedException {
        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
                .loadAnimation(any(), eq(false));
        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
                .loadAnimation(any(), eq(true));

        mCustomizeActivityAnimation.prepareNextAnimation(
                new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
        final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -112,6 +120,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {

    @Test
    public void receiveFinishAfterCancel() throws InterruptedException {
        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
                .loadAnimation(any(), eq(false));
        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
                .loadAnimation(any(), eq(true));

        mCustomizeActivityAnimation.prepareNextAnimation(
                new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
        final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -152,4 +166,67 @@ public class CustomizeActivityAnimationTest extends ShellTestCase {
        verify(mCustomizeActivityAnimation).onGestureCommitted();
        finishCalled.await(1, TimeUnit.SECONDS);
    }

    @Test
    public void testLoadCustomAnimation() {
        testLoadCustomAnimation(10, 20, 0);
    }

    @Test
    public void testLoadCustomAnimationNoEnter() {
        testLoadCustomAnimation(0, 10, 0);
    }

    @Test
    public void testLoadWindowAnimations() {
        testLoadCustomAnimation(0, 0, 30);
    }

    @Test
    public void testCustomAnimationHigherThanWindowAnimations() {
        testLoadCustomAnimation(10, 20, 30);
    }

    private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) {
        final String testPackage = "TestPackage";
        BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
                .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN)
                .setWindowAnimations(testPackage, windowAnimations);
        final BackNavigationInfo.CustomAnimationInfo info = builder.build()
                .getCustomAnimationInfo();

        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
                        .mTransitionAnimation)
                .loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
                        .mTransitionAnimation)
                .loadAppTransitionAnimation(eq(testPackage), eq(exitResId));
        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
                        .mTransitionAnimation)
                .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean());
        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
                        .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean());

        CustomizeActivityAnimation.AnimationLoadResult result =
                mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info);

        if (exitResId != 0) {
            if (enterResId == 0) {
                verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
                        never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
                verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation)
                        .loadDefaultAnimationAttr(anyInt(), anyBoolean());
            } else {
                assertEquals(result.mEnterAnimation, mMockOpenAnimation);
            }
            assertEquals(result.mBackgroundColor, Color.GREEN);
            assertEquals(result.mCloseAnimation, mMockCloseAnimation);
            verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never())
                    .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
        } else if (windowAnimations != 0) {
            verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
                    times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
            assertEquals(result.mCloseAnimation, mMockCloseAnimation);
        }
    }
}
+15 −6
Original line number Diff line number Diff line
@@ -262,15 +262,24 @@ class BackNavigationController {
                if (!isOccluded || prevActivity.canShowWhenLocked()) {
                    // We have another Activity in the same currentTask to go to
                    final WindowContainer parent = currentActivity.getParent();
                    final boolean isCustomize = parent != null
                    final boolean canCustomize = parent != null
                            && (parent.asTask() != null
                            || (parent.asTaskFragment() != null
                            && parent.canCustomizeAppTransition()))
                            && isCustomizeExitAnimation(window);
                    if (isCustomize) {
                            && parent.canCustomizeAppTransition()));
                    if (canCustomize) {
                        if (isCustomizeExitAnimation(window)) {
                            infoBuilder.setWindowAnimations(
                                    window.mAttrs.packageName, window.mAttrs.windowAnimations);
                        }
                        final ActivityRecord.CustomAppTransition customAppTransition =
                                currentActivity.getCustomAnimation(false/* open */);
                        if (customAppTransition != null) {
                            infoBuilder.setCustomAnimation(currentActivity.packageName,
                                    customAppTransition.mExitAnim,
                                    customAppTransition.mEnterAnim,
                                    customAppTransition.mBackgroundColor);
                        }
                    }
                    removedWindowContainer = currentActivity;
                    prevTask = prevActivity.getTask();
                    backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;