From 5e0c72a2c593de712debf7294e39fb36553a2b30 Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Tue, 8 Feb 2022 10:06:28 +0800 Subject: [PATCH] Estimate animation duration for AVD and AnimationDrawable For Android S, we use the API windowSplashScreenAnimationDuration to know the duration of the animatable icon would be, however, developers can be confused to thought that this API is used to declare the duration of splash screen. To get the real duration of the animatable icon, if it is an AVD or AnimationDrawable, try to get the animation duration from it. Test: atest StartingSurfaceDrawerTests SplashscreenTests Bug: 211707095 Change-Id: Ia3f8c5cd50bab164360e07d9ced87ff7f4321e96 --- .../java/android/window/SplashScreenView.java | 11 +- .../drawable/AnimatedVectorDrawable.java | 27 +++- .../graphics/drawable/AnimationDrawable.java | 19 +++ .../SplashscreenContentDrawer.java | 14 +- .../SplashscreenIconDrawableFactory.java | 152 ++++++++++-------- .../StartingSurfaceDrawer.java | 3 +- 6 files changed, 144 insertions(+), 82 deletions(-) diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 34a34180b2275..232248b6fab34 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -65,6 +65,7 @@ import com.android.internal.util.ContrastColorUtil; import java.time.Duration; import java.time.Instant; import java.util.function.Consumer; +import java.util.function.LongConsumer; /** *

The view which allows an activity to customize its splash screen exit animation.

@@ -234,7 +235,7 @@ public final class SplashScreenView extends FrameLayout { /** * Set the animation duration if icon is animatable. */ - public Builder setAnimationDurationMillis(int duration) { + public Builder setAnimationDurationMillis(long duration) { mIconAnimationDuration = Duration.ofMillis(duration); return this; } @@ -521,8 +522,11 @@ public final class SplashScreenView extends FrameLayout { }); } - private void animationStartCallback() { + private void animationStartCallback(long animDuration) { mIconAnimationStart = Instant.now(); + if (animDuration > 0) { + mIconAnimationDuration = Duration.ofMillis(animDuration); + } } /** @@ -693,9 +697,8 @@ public final class SplashScreenView extends FrameLayout { * Prepare the animation if this drawable also be animatable. * @param duration The animation duration. * @param startListener The callback listener used to receive the start of the animation. - * @return true if this drawable object can also be animated and it can be played now. */ - boolean prepareAnimate(long duration, Runnable startListener); + void prepareAnimate(long duration, LongConsumer startListener); /** * Stop animation. diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 33b09b8831ce8..55f205bb14a6e 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -690,6 +690,14 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } } + /** + * Gets the total duration of the animation + * @hide + */ + public long getTotalDuration() { + return mAnimatorSet.getTotalDuration(); + } + private static class AnimatedVectorDrawableState extends ConstantState { @Config int mChangingConfigurations; VectorDrawable mVectorDrawable; @@ -1074,6 +1082,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { boolean isInfinite(); void pause(); void resume(); + long getTotalDuration(); } private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator { @@ -1085,6 +1094,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { // setup by init(). private ArrayList mListenerArray = null; private boolean mIsInfinite = false; + private long mTotalDuration; VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) { mDrawable = drawable; @@ -1100,7 +1110,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { // Keep a deep copy of the set, such that set can be still be constantly representing // the static content from XML file. mSet = set.clone(); - mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE; + mTotalDuration = mSet.getTotalDuration(); + mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE; // If there are listeners added before calling init(), now they should be setup. if (mListenerArray != null && !mListenerArray.isEmpty()) { @@ -1219,6 +1230,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private void invalidateOwningView() { mDrawable.invalidateSelf(); } + + @Override + public long getTotalDuration() { + return mTotalDuration; + } } /** @@ -1249,6 +1265,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private int mLastListenerId = 0; private final IntArray mPendingAnimationActions = new IntArray(); private final AnimatedVectorDrawable mDrawable; + private long mTotalDuration; VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { mDrawable = drawable; @@ -1270,7 +1287,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { .getNativeTree(); nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr); mInitialized = true; - mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE; + mTotalDuration = set.getTotalDuration(); + mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE; // Check reversible. mIsReversible = true; @@ -1796,6 +1814,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } mPendingAnimationActions.clear(); } + + @Override + public long getTotalDuration() { + return mTotalDuration; + } } private static native long nCreateAnimatorSet(); diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 8c3fa441cbb04..7fd2201e71081 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -424,6 +424,17 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An System.arraycopy(mDurations, 0, newDurations, 0, oldSize); mDurations = newDurations; } + + public long getTotalDuration() { + if (mDurations != null) { + int total = 0; + for (int dur : mDurations) { + total += dur; + } + return total; + } + return 0; + } } @Override @@ -435,6 +446,14 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An } } + /** + * Gets the total duration of the animation + * @hide + */ + public long getTotalDuration() { + return mAnimationState.getTotalDuration(); + } + private AnimationDrawable(AnimationState state, Resources res) { final AnimationState as = new AnimationState(state, this, res); setConstantState(as); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 025bcad079556..33aa018923a60 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -69,6 +69,7 @@ import com.android.internal.graphics.palette.VariationalKMeansQuantizer; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -115,8 +116,10 @@ public class SplashscreenContentDrawer { private final Handler mSplashscreenWorkerHandler; @VisibleForTesting final ColorCache mColorCache; + private final ShellExecutor mSplashScreenExecutor; - SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) { + SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool, + ShellExecutor splashScreenExecutor) { mContext = context; mIconProvider = iconProvider; mTransactionPool = pool; @@ -129,6 +132,7 @@ public class SplashscreenContentDrawer { shellSplashscreenWorkerThread.start(); mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler(); mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler); + mSplashScreenExecutor = splashScreenExecutor; } /** @@ -397,7 +401,7 @@ public class SplashscreenContentDrawer { SplashScreenView build() { Drawable iconDrawable; - final int animationDuration; + final long animationDuration; if (mSuggestType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { // empty or legacy splash screen case @@ -455,8 +459,8 @@ public class SplashscreenContentDrawer { iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler); } else { mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable( - mTmpAttrs.mIconBgColor, mThemeColor, - iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler); + mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize, + mFinalIconSize, mSplashscreenWorkerHandler, mSplashScreenExecutor); } } @@ -516,7 +520,7 @@ public class SplashscreenContentDrawer { } private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable, - int animationDuration, Consumer uiThreadInitTask) { + long animationDuration, Consumer uiThreadInitTask) { Drawable foreground = null; Drawable background = null; if (iconDrawable != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index 54281e0199e3b..fdd5a1578f41d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -18,9 +18,7 @@ package com.android.wm.shell.startingsurface; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,6 +34,8 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Animatable; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Trace; @@ -44,6 +44,9 @@ import android.util.PathParser; import android.window.SplashScreenView; import com.android.internal.R; +import com.android.wm.shell.common.ShellExecutor; + +import java.util.function.LongConsumer; /** * Creating a lightweight Drawable object used for splash screen. @@ -60,14 +63,15 @@ public class SplashscreenIconDrawableFactory { */ static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor, @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize, - Handler splashscreenWorkerHandler) { + Handler splashscreenWorkerHandler, ShellExecutor splashScreenExecutor) { Drawable foreground; Drawable background = null; boolean drawBackground = backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor; if (foregroundDrawable instanceof Animatable) { - foreground = new AnimatableIconAnimateListener(foregroundDrawable); + foreground = new AnimatableIconAnimateListener(foregroundDrawable, + splashScreenExecutor); } else if (foregroundDrawable instanceof AdaptiveIconDrawable) { // If the icon is Adaptive, we already use the icon background. drawBackground = false; @@ -266,14 +270,37 @@ public class SplashscreenIconDrawableFactory { */ public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable implements SplashScreenView.IconAnimateListener { - private Animatable mAnimatableIcon; - private Animator mIconAnimator; + private final Animatable mAnimatableIcon; private boolean mAnimationTriggered; private AnimatorListenerAdapter mJankMonitoringListener; + private boolean mRunning; + private long mDuration; + private LongConsumer mStartListener; + private final ShellExecutor mSplashScreenExecutor; - AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) { + AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable, + ShellExecutor splashScreenExecutor) { super(foregroundDrawable); - mForegroundDrawable.setCallback(mCallback); + Callback callback = new Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, + long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } + }; + mForegroundDrawable.setCallback(callback); + mSplashScreenExecutor = splashScreenExecutor; + mAnimatableIcon = (Animatable) mForegroundDrawable; } @Override @@ -282,83 +309,68 @@ public class SplashscreenIconDrawableFactory { } @Override - public boolean prepareAnimate(long duration, Runnable startListener) { - mAnimatableIcon = (Animatable) mForegroundDrawable; - mIconAnimator = ValueAnimator.ofInt(0, 1); - mIconAnimator.setDuration(duration); - mIconAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - if (startListener != null) { - startListener.run(); - } - try { - if (mJankMonitoringListener != null) { - mJankMonitoringListener.onAnimationStart(animation); - } - mAnimatableIcon.start(); - } catch (Exception ex) { - Log.e(TAG, "Error while running the splash screen animated icon", ex); - animation.cancel(); - } - } + public void prepareAnimate(long duration, LongConsumer startListener) { + stopAnimation(); + mDuration = duration; + mStartListener = startListener; + } - @Override - public void onAnimationEnd(Animator animation) { - mAnimatableIcon.stop(); - if (mJankMonitoringListener != null) { - mJankMonitoringListener.onAnimationEnd(animation); - } + private void startAnimation() { + if (mJankMonitoringListener != null) { + mJankMonitoringListener.onAnimationStart(null); + } + try { + mAnimatableIcon.start(); + } catch (Exception ex) { + Log.e(TAG, "Error while running the splash screen animated icon", ex); + mRunning = false; + if (mJankMonitoringListener != null) { + mJankMonitoringListener.onAnimationCancel(null); } + if (mStartListener != null) { + mStartListener.accept(mDuration); + } + return; + } + long animDuration = mDuration; + if (mAnimatableIcon instanceof AnimatedVectorDrawable + && ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration() > 0) { + animDuration = ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration(); + } else if (mAnimatableIcon instanceof AnimationDrawable + && ((AnimationDrawable) mAnimatableIcon).getTotalDuration() > 0) { + animDuration = ((AnimationDrawable) mAnimatableIcon).getTotalDuration(); + } + mRunning = true; + mSplashScreenExecutor.executeDelayed(this::stopAnimation, animDuration); + if (mStartListener != null) { + mStartListener.accept(Math.max(animDuration, 0)); + } + } - @Override - public void onAnimationCancel(Animator animation) { - mAnimatableIcon.stop(); - if (mJankMonitoringListener != null) { - mJankMonitoringListener.onAnimationCancel(animation); - } - } - - @Override - public void onAnimationRepeat(Animator animation) { - // do not repeat - mAnimatableIcon.stop(); - } - }); - return true; + private void onAnimationEnd() { + mAnimatableIcon.stop(); + if (mJankMonitoringListener != null) { + mJankMonitoringListener.onAnimationEnd(null); + } + mStartListener = null; + mRunning = false; } @Override public void stopAnimation() { - if (mIconAnimator != null && mIconAnimator.isRunning()) { - mIconAnimator.end(); + if (mRunning) { + mSplashScreenExecutor.removeCallbacks(this::stopAnimation); + onAnimationEnd(); mJankMonitoringListener = null; } } - private final Callback mCallback = new Callback() { - @Override - public void invalidateDrawable(@NonNull Drawable who) { - invalidateSelf(); - } - - @Override - public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { - scheduleSelf(what, when); - } - - @Override - public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { - unscheduleSelf(what); - } - }; - private void ensureAnimationStarted() { if (mAnimationTriggered) { return; } - if (mIconAnimator != null && !mIconAnimator.isRunning()) { - mIconAnimator.start(); + if (!mRunning) { + startAnimation(); } mAnimationTriggered = true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 34426991a459b..558a1fb694c32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -148,7 +148,8 @@ public class StartingSurfaceDrawer { mContext = context; mDisplayManager = mContext.getSystemService(DisplayManager.class); mSplashScreenExecutor = splashScreenExecutor; - mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool); + mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool, + mSplashScreenExecutor); mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance()); mWindowManagerGlobal = WindowManagerGlobal.getInstance(); mDisplayManager.getDisplay(DEFAULT_DISPLAY);