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

Commit ba7dab1b authored by Tony Huang's avatar Tony Huang Committed by Automerger Merge Worker
Browse files

Merge "Make AppTransition animations sharable (2/n)" into sc-dev am: f9a0ccc0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13425250

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I6d9b0286583e5b8c061826faf23c63c4224fb5ca
parents 5326366d f9a0ccc0
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -180,6 +180,7 @@ public class TransitionAnimation {
                "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
    }

    /** Load animation by resource Id from specific LayoutParams. */
    @Nullable
    private Animation loadAnimationRes(LayoutParams lp, int resId) {
        Context context = mContext;
@@ -193,6 +194,7 @@ public class TransitionAnimation {
        return null;
    }

    /** Load animation by resource Id from specific package. */
    @Nullable
    private Animation loadAnimationRes(String packageName, int resId) {
        if (ResourceId.isValid(resId)) {
@@ -204,6 +206,13 @@ public class TransitionAnimation {
        return null;
    }

    /** Load animation by resource Id from android package. */
    @Nullable
    public Animation loadDefaultAnimationRes(int resId) {
        return loadAnimationRes("android", resId);
    }

    /** Load animation by attribute Id from specific LayoutParams */
    @Nullable
    public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
        int resId = Resources.ID_NULL;
@@ -222,6 +231,25 @@ public class TransitionAnimation {
        return null;
    }

    /** Load animation by attribute Id from android package. */
    @Nullable
    public Animation loadDefaultAnimationAttr(int animAttr) {
        int resId = Resources.ID_NULL;
        Context context = mContext;
        if (animAttr >= 0) {
            AttributeCache.Entry ent = getCachedAnimations("android",
                    mDefaultWindowAnimationStyleResId);
            if (ent != null) {
                context = ent.context;
                resId = ent.array.getResourceId(animAttr, 0);
            }
        }
        if (ResourceId.isValid(resId)) {
            return loadAnimationSafely(context, resId, mTag);
        }
        return null;
    }

    @Nullable
    private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
        if (mDebug) {
+114 −34
Original line number Diff line number Diff line
@@ -16,55 +16,78 @@

package com.android.wm.shell.transition;

import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import com.android.internal.R;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;

import java.util.ArrayList;

/** The default handler that handles anything not already handled. */
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
    private static final int MAX_ANIMATION_DURATION = 3000;

    private final TransactionPool mTransactionPool;
    private final ShellExecutor mMainExecutor;
    private final ShellExecutor mAnimExecutor;
    private final TransitionAnimation mTransitionAnimation;

    /** Keeps track of the currently-running animations associated with each transition. */
    private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();

    DefaultTransitionHandler(@NonNull TransactionPool transactionPool,
    private float mTransitionAnimationScaleSetting = 1.0f;

    DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
        mTransactionPool = transactionPool;
        mMainExecutor = mainExecutor;
        mAnimExecutor = animExecutor;
        mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);

        AttributeCache.init(context);
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                "start default transition animation, info = %s", info);
        if (mAnimations.containsKey(transition)) {
            throw new IllegalStateException("Got a duplicate startAnimation call for "
                    + transition);
        }
        final ArrayList<Animator> animations = new ArrayList<>();
        mAnimations.put(transition, animations);
        final boolean isOpening = Transitions.isOpeningType(info.getType());

        final Runnable onAnimFinish = () -> {
            if (!animations.isEmpty()) return;
@@ -77,19 +100,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            // Don't animate anything with an animating parent
            if (change.getParent() != null) continue;

            final int mode = change.getMode();
            if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
                if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                    // This received a transferred starting window, so don't animate
                    continue;
                }
                // fade in
                startExampleAnimation(
                        animations, change.getLeash(), true /* show */, onAnimFinish);
            } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
                // fade out
                startExampleAnimation(
                        animations, change.getLeash(), false /* show */, onAnimFinish);
            Animation a = loadAnimation(info.getType(), change);
            if (a != null) {
                startAnimInternal(animations, a, change.getLeash(), onAnimFinish);
            }
        }
        t.apply();
@@ -105,32 +118,93 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
        return null;
    }

    // TODO(shell-transitions): real animations
    private void startExampleAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull SurfaceControl leash, boolean show, @NonNull Runnable finishCallback) {
        final float end = show ? 1.f : 0.f;
        final float start = 1.f - end;
    @Override
    public void setAnimScaleSetting(float scale) {
        mTransitionAnimationScaleSetting = scale;
    }

    @Nullable
    private Animation loadAnimation(int type, TransitionInfo.Change change) {
        // TODO(b/178678389): It should handle more type animation here
        Animation a = null;

        final boolean isOpening = Transitions.isOpeningType(type);
        final int mode = change.getMode();
        final int flags = change.getFlags();

        if (mode == TRANSIT_OPEN && isOpening) {
            if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                // This received a transferred starting window, so don't animate
                return null;
            }

            if (change.getTaskInfo() != null) {
                a = mTransitionAnimation.loadDefaultAnimationAttr(
                        R.styleable.WindowAnimation_taskOpenEnterAnimation);
            } else {
                a = mTransitionAnimation.loadDefaultAnimationRes((flags & FLAG_TRANSLUCENT) == 0
                        ? R.anim.activity_open_enter : R.anim.activity_translucent_open_enter);
            }
        } else if (mode == TRANSIT_TO_FRONT && isOpening) {
            if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                // This received a transferred starting window, so don't animate
                return null;
            }

            a = mTransitionAnimation.loadDefaultAnimationAttr(
                    R.styleable.WindowAnimation_taskToFrontEnterAnimation);
        } else if (mode == TRANSIT_CLOSE && !isOpening) {
            if (change.getTaskInfo() != null) {
                a = mTransitionAnimation.loadDefaultAnimationAttr(
                        R.styleable.WindowAnimation_taskCloseExitAnimation);
            } else {
                a = mTransitionAnimation.loadDefaultAnimationRes((flags & FLAG_TRANSLUCENT) == 0
                        ? R.anim.activity_close_exit : R.anim.activity_translucent_close_exit);
            }
        } else if (mode == TRANSIT_TO_BACK && !isOpening) {
            a = mTransitionAnimation.loadDefaultAnimationAttr(
                    R.styleable.WindowAnimation_taskToBackExitAnimation);
        } else if (mode == TRANSIT_CHANGE) {
            // In the absence of a specific adapter, we just want to keep everything stationary.
            a = new AlphaAnimation(1.f, 1.f);
            a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
        }

        if (a != null) {
            Rect start = change.getStartAbsBounds();
            Rect end = change.getEndAbsBounds();
            a.restrictDuration(MAX_ANIMATION_DURATION);
            a.initialize(end.width(), end.height(), start.width(), start.height());
            a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
        }
        return a;
    }

    private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim,
            @NonNull SurfaceControl leash, @NonNull Runnable finishCallback) {
        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
        final ValueAnimator va = ValueAnimator.ofFloat(start, end);
        va.setDuration(500);
        final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
        final Transformation transformation = new Transformation();
        final float[] matrix = new float[9];
        // Animation length is already expected to be scaled.
        va.overrideDurationScale(1.0f);
        va.setDuration(anim.computeDurationHint());
        va.addUpdateListener(animation -> {
            float fraction = animation.getAnimatedFraction();
            transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
            transaction.apply();
            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());

            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix);
        });

        final Runnable finisher = () -> {
            transaction.setAlpha(leash, end);
            transaction.apply();
            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix);

            mTransactionPool.release(transaction);
            mMainExecutor.execute(() -> {
                animations.remove(va);
                finishCallback.run();
            });
        };
        va.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) { }

        va.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                finisher.run();
@@ -140,11 +214,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            public void onAnimationCancel(Animator animation) {
                finisher.run();
            }

            @Override
            public void onAnimationRepeat(Animator animation) { }
        });
        animations.add(va);
        mAnimExecutor.execute(va::start);
    }

    private static void applyTransformation(long time, SurfaceControl.Transaction t,
            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix) {
        anim.getTransformation(time, transformation);
        t.setMatrix(leash, transformation.getMatrix(), matrix);
        t.setAlpha(leash, transformation.getAlpha());
        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
        t.apply();
    }
}
+54 −2
Original line number Diff line number Diff line
@@ -25,9 +25,13 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
@@ -43,6 +47,7 @@ import android.window.WindowOrganizer;

import androidx.annotation.BinderThread;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -63,6 +68,7 @@ public class Transitions {
            SystemProperties.getBoolean("persist.debug.shell_transit", false);

    private final WindowOrganizer mOrganizer;
    private final Context mContext;
    private final ShellExecutor mMainExecutor;
    private final ShellExecutor mAnimExecutor;
    private final TransitionPlayerImpl mPlayerImpl;
@@ -72,6 +78,8 @@ public class Transitions {
    /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
    private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();

    private float mTransitionAnimationScaleSetting = 1.0f;

    private static final class ActiveTransition {
        TransitionHandler mFirstHandler = null;
    }
@@ -84,26 +92,46 @@ public class Transitions {
    }

    public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
            @NonNull Context context, @NonNull ShellExecutor mainExecutor,
            @NonNull ShellExecutor animExecutor) {
        mOrganizer = organizer;
        mContext = context;
        mMainExecutor = mainExecutor;
        mAnimExecutor = animExecutor;
        mPlayerImpl = new TransitionPlayerImpl();
        // The very last handler (0 in the list) should be the default one.
        mHandlers.add(new DefaultTransitionHandler(pool, mainExecutor, animExecutor));
        mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
        // Next lowest priority is remote transitions.
        mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
        mHandlers.add(mRemoteTransitionHandler);

        ContentResolver resolver = context.getContentResolver();
        mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
                Settings.Global.TRANSITION_ANIMATION_SCALE,
                context.getResources().getFloat(
                        R.dimen.config_appTransitionAnimationDurationScaleDefault));
        dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);

        resolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
                new SettingsObserver());
    }

    private Transitions() {
        mOrganizer = null;
        mContext = null;
        mMainExecutor = null;
        mAnimExecutor = null;
        mPlayerImpl = null;
        mRemoteTransitionHandler = null;
    }

    private void dispatchAnimScaleSetting(float scale) {
        for (int i = mHandlers.size() - 1; i >= 0; --i) {
            mHandlers.get(i).setAnimScaleSetting(scale);
        }
    }

    /** Create an empty/non-registering transitions object for system-ui tests. */
    @VisibleForTesting
    public static RemoteTransitions createEmptyForTesting() {
@@ -368,6 +396,13 @@ public class Transitions {
        @Nullable
        WindowContainerTransaction handleRequest(@NonNull IBinder transition,
                @NonNull TransitionRequestInfo request);

        /**
         * Sets transition animation scale settings value to handler.
         *
         * @param scale The setting value of transition animation scale.
         */
        default void setAnimScaleSetting(float scale) {}
    }

    @BinderThread
@@ -404,4 +439,21 @@ public class Transitions {
            });
        }
    }

    private class SettingsObserver extends ContentObserver {

        SettingsObserver() {
            super(null);
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            mTransitionAnimationScaleSetting = Settings.Global.getFloat(
                    mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
                    mTransitionAnimationScaleSetting);

            mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
        }
    }
}
+12 −8
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -58,6 +59,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
@@ -78,6 +80,8 @@ public class ShellTransitionTests {

    private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
    private final TransactionPool mTransactionPool = mock(TransactionPool.class);
    private final Context mContext =
            InstrumentationRegistry.getInstrumentation().getTargetContext();
    private final TestShellExecutor mMainExecutor = new TestShellExecutor();
    private final ShellExecutor mAnimExecutor = new TestShellExecutor();
    private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
@@ -90,8 +94,8 @@ public class ShellTransitionTests {

    @Test
    public void testBasicTransitionFlow() {
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
                mAnimExecutor);
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
                mMainExecutor, mAnimExecutor);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        IBinder transitToken = new Binder();
@@ -109,8 +113,8 @@ public class ShellTransitionTests {

    @Test
    public void testNonDefaultHandler() {
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
                mAnimExecutor);
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
                mMainExecutor, mAnimExecutor);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
@@ -188,8 +192,8 @@ public class ShellTransitionTests {

    @Test
    public void testRequestRemoteTransition() {
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
                mAnimExecutor);
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
                mMainExecutor, mAnimExecutor);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        final boolean[] remoteCalled = new boolean[]{false};
@@ -255,8 +259,8 @@ public class ShellTransitionTests {

    @Test
    public void testRegisteredRemoteTransition() {
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
                mAnimExecutor);
        Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
                mMainExecutor, mAnimExecutor);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        final boolean[] remoteCalled = new boolean[]{false};
+2 −2
Original line number Diff line number Diff line
@@ -380,9 +380,9 @@ public abstract class WMShellBaseModule {
    @WMSingleton
    @Provides
    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
            @ShellMainThread ShellExecutor mainExecutor,
            Context context, @ShellMainThread ShellExecutor mainExecutor,
            @ShellAnimationThread ShellExecutor animExecutor) {
        return new Transitions(organizer, pool, mainExecutor, animExecutor);
        return new Transitions(organizer, pool, context, mainExecutor, animExecutor);
    }

    //