diff options
| author | Wei Sheng Shih <wilsonshih@google.com> | 2022-03-02 06:24:50 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2022-03-02 06:24:50 +0000 |
| commit | 172ea326deb316b052bed4975eaefe8bb8d45bee (patch) | |
| tree | 828fa7b793151d79a3b1f688fba7205ddaaa76a2 | |
| parent | 96c65d7c31042f02c48b1fa22647a194592129e0 (diff) | |
| parent | 5e0c72a2c593de712debf7294e39fb36553a2b30 (diff) | |
Merge "Estimate animation duration for AVD and AnimationDrawable" into tm-dev
6 files changed, 143 insertions, 81 deletions
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 34a34180b227..232248b6fab3 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; /** * <p>The view which allows an activity to customize its splash screen exit animation.</p> @@ -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 33b09b8831ce..55f205bb14a6 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<AnimatorListener> 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 8c3fa441cbb0..7fd2201e7108 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 025bcad07955..33aa018923a6 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<Runnable> uiThreadInitTask) { + long animationDuration, Consumer<Runnable> 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 54281e0199e3..fdd5a1578f41 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,99 +270,107 @@ 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); - } - - @Override - public void setAnimationJankMonitoring(AnimatorListenerAdapter listener) { - mJankMonitoringListener = listener; - } - - @Override - public boolean prepareAnimate(long duration, Runnable startListener) { - mAnimatableIcon = (Animatable) mForegroundDrawable; - mIconAnimator = ValueAnimator.ofInt(0, 1); - mIconAnimator.setDuration(duration); - mIconAnimator.addListener(new Animator.AnimatorListener() { + Callback callback = new Callback() { @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 invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); } @Override - public void onAnimationEnd(Animator animation) { - mAnimatableIcon.stop(); - if (mJankMonitoringListener != null) { - mJankMonitoringListener.onAnimationEnd(animation); - } + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, + long when) { + scheduleSelf(what, when); } @Override - public void onAnimationCancel(Animator animation) { - mAnimatableIcon.stop(); - if (mJankMonitoringListener != null) { - mJankMonitoringListener.onAnimationCancel(animation); - } + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); } + }; + mForegroundDrawable.setCallback(callback); + mSplashScreenExecutor = splashScreenExecutor; + mAnimatableIcon = (Animatable) mForegroundDrawable; + } - @Override - public void onAnimationRepeat(Animator animation) { - // do not repeat - mAnimatableIcon.stop(); - } - }); - return true; + @Override + public void setAnimationJankMonitoring(AnimatorListenerAdapter listener) { + mJankMonitoringListener = listener; } @Override - public void stopAnimation() { - if (mIconAnimator != null && mIconAnimator.isRunning()) { - mIconAnimator.end(); - mJankMonitoringListener = null; - } + public void prepareAnimate(long duration, LongConsumer startListener) { + stopAnimation(); + mDuration = duration; + mStartListener = startListener; } - private final Callback mCallback = new Callback() { - @Override - public void invalidateDrawable(@NonNull Drawable who) { - invalidateSelf(); + 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 scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { - scheduleSelf(what, when); + private void onAnimationEnd() { + mAnimatableIcon.stop(); + if (mJankMonitoringListener != null) { + mJankMonitoringListener.onAnimationEnd(null); } + mStartListener = null; + mRunning = false; + } - @Override - public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { - unscheduleSelf(what); + @Override + public void stopAnimation() { + if (mRunning) { + mSplashScreenExecutor.removeCallbacks(this::stopAnimation); + onAnimationEnd(); + mJankMonitoringListener = null; } - }; + } 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 61cbf6e3c93c..04d6ef7f9505 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); |
