diff options
5 files changed, 210 insertions, 46 deletions
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 56b25b2060ea..93ba0372df08 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -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) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 59f8c1df1213..2182ee5590e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -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(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 10344892e766..89eee67bf5af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -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)); + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 5eca3e75a7b8..926108c41e5e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -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}; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index fba0b0079012..5d9465904e3b 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -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); } // |