Merge "Estimate animation duration for AVD and AnimationDrawable" into tm-dev

This commit is contained in:
Wei Sheng Shih
2022-03-02 06:24:50 +00:00
committed by Android (Google) Code Review
6 changed files with 144 additions and 82 deletions

View File

@@ -65,6 +65,7 @@ import com.android.internal.util.ContrastColorUtil;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.function.Consumer; 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> * <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. * Set the animation duration if icon is animatable.
*/ */
public Builder setAnimationDurationMillis(int duration) { public Builder setAnimationDurationMillis(long duration) {
mIconAnimationDuration = Duration.ofMillis(duration); mIconAnimationDuration = Duration.ofMillis(duration);
return this; return this;
} }
@@ -521,8 +522,11 @@ public final class SplashScreenView extends FrameLayout {
}); });
} }
private void animationStartCallback() { private void animationStartCallback(long animDuration) {
mIconAnimationStart = Instant.now(); 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. * Prepare the animation if this drawable also be animatable.
* @param duration The animation duration. * @param duration The animation duration.
* @param startListener The callback listener used to receive the start of the animation. * @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. * Stop animation.

View File

@@ -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 { private static class AnimatedVectorDrawableState extends ConstantState {
@Config int mChangingConfigurations; @Config int mChangingConfigurations;
VectorDrawable mVectorDrawable; VectorDrawable mVectorDrawable;
@@ -1074,6 +1082,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
boolean isInfinite(); boolean isInfinite();
void pause(); void pause();
void resume(); void resume();
long getTotalDuration();
} }
private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator { private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator {
@@ -1085,6 +1094,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
// setup by init(). // setup by init().
private ArrayList<AnimatorListener> mListenerArray = null; private ArrayList<AnimatorListener> mListenerArray = null;
private boolean mIsInfinite = false; private boolean mIsInfinite = false;
private long mTotalDuration;
VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) { VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) {
mDrawable = 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 // Keep a deep copy of the set, such that set can be still be constantly representing
// the static content from XML file. // the static content from XML file.
mSet = set.clone(); 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 there are listeners added before calling init(), now they should be setup.
if (mListenerArray != null && !mListenerArray.isEmpty()) { if (mListenerArray != null && !mListenerArray.isEmpty()) {
@@ -1219,6 +1230,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
private void invalidateOwningView() { private void invalidateOwningView() {
mDrawable.invalidateSelf(); mDrawable.invalidateSelf();
} }
@Override
public long getTotalDuration() {
return mTotalDuration;
}
} }
/** /**
@@ -1249,6 +1265,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
private int mLastListenerId = 0; private int mLastListenerId = 0;
private final IntArray mPendingAnimationActions = new IntArray(); private final IntArray mPendingAnimationActions = new IntArray();
private final AnimatedVectorDrawable mDrawable; private final AnimatedVectorDrawable mDrawable;
private long mTotalDuration;
VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
mDrawable = drawable; mDrawable = drawable;
@@ -1270,7 +1287,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
.getNativeTree(); .getNativeTree();
nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr); nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr);
mInitialized = true; mInitialized = true;
mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE; mTotalDuration = set.getTotalDuration();
mIsInfinite = mTotalDuration == Animator.DURATION_INFINITE;
// Check reversible. // Check reversible.
mIsReversible = true; mIsReversible = true;
@@ -1796,6 +1814,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
} }
mPendingAnimationActions.clear(); mPendingAnimationActions.clear();
} }
@Override
public long getTotalDuration() {
return mTotalDuration;
}
} }
private static native long nCreateAnimatorSet(); private static native long nCreateAnimatorSet();

View File

@@ -424,6 +424,17 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
System.arraycopy(mDurations, 0, newDurations, 0, oldSize); System.arraycopy(mDurations, 0, newDurations, 0, oldSize);
mDurations = newDurations; mDurations = newDurations;
} }
public long getTotalDuration() {
if (mDurations != null) {
int total = 0;
for (int dur : mDurations) {
total += dur;
}
return total;
}
return 0;
}
} }
@Override @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) { private AnimationDrawable(AnimationState state, Resources res) {
final AnimationState as = new AnimationState(state, this, res); final AnimationState as = new AnimationState(state, this, res);
setConstantState(as); setConstantState(as);

View File

@@ -69,6 +69,7 @@ import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider; 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.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -115,8 +116,10 @@ public class SplashscreenContentDrawer {
private final Handler mSplashscreenWorkerHandler; private final Handler mSplashscreenWorkerHandler;
@VisibleForTesting @VisibleForTesting
final ColorCache mColorCache; 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; mContext = context;
mIconProvider = iconProvider; mIconProvider = iconProvider;
mTransactionPool = pool; mTransactionPool = pool;
@@ -129,6 +132,7 @@ public class SplashscreenContentDrawer {
shellSplashscreenWorkerThread.start(); shellSplashscreenWorkerThread.start();
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler(); mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler); mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
mSplashScreenExecutor = splashScreenExecutor;
} }
/** /**
@@ -397,7 +401,7 @@ public class SplashscreenContentDrawer {
SplashScreenView build() { SplashScreenView build() {
Drawable iconDrawable; Drawable iconDrawable;
final int animationDuration; final long animationDuration;
if (mSuggestType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN if (mSuggestType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
|| mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
// empty or legacy splash screen case // empty or legacy splash screen case
@@ -455,8 +459,8 @@ public class SplashscreenContentDrawer {
iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler); iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler);
} else { } else {
mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable( mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
mTmpAttrs.mIconBgColor, mThemeColor, mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler); mFinalIconSize, mSplashscreenWorkerHandler, mSplashScreenExecutor);
} }
} }
@@ -516,7 +520,7 @@ public class SplashscreenContentDrawer {
} }
private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable, private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
int animationDuration, Consumer<Runnable> uiThreadInitTask) { long animationDuration, Consumer<Runnable> uiThreadInitTask) {
Drawable foreground = null; Drawable foreground = null;
Drawable background = null; Drawable background = null;
if (iconDrawable != null) { if (iconDrawable != null) {

View File

@@ -18,9 +18,7 @@ package com.android.wm.shell.startingsurface;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.ColorInt; import android.annotation.ColorInt;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -36,6 +34,8 @@ import android.graphics.PixelFormat;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.os.Trace; import android.os.Trace;
@@ -44,6 +44,9 @@ import android.util.PathParser;
import android.window.SplashScreenView; import android.window.SplashScreenView;
import com.android.internal.R; 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. * 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, static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
@NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize, @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
Handler splashscreenWorkerHandler) { Handler splashscreenWorkerHandler, ShellExecutor splashScreenExecutor) {
Drawable foreground; Drawable foreground;
Drawable background = null; Drawable background = null;
boolean drawBackground = boolean drawBackground =
backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor; backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor;
if (foregroundDrawable instanceof Animatable) { if (foregroundDrawable instanceof Animatable) {
foreground = new AnimatableIconAnimateListener(foregroundDrawable); foreground = new AnimatableIconAnimateListener(foregroundDrawable,
splashScreenExecutor);
} else if (foregroundDrawable instanceof AdaptiveIconDrawable) { } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
// If the icon is Adaptive, we already use the icon background. // If the icon is Adaptive, we already use the icon background.
drawBackground = false; drawBackground = false;
@@ -266,14 +270,37 @@ public class SplashscreenIconDrawableFactory {
*/ */
public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable public static class AnimatableIconAnimateListener extends AdaptiveForegroundDrawable
implements SplashScreenView.IconAnimateListener { implements SplashScreenView.IconAnimateListener {
private Animatable mAnimatableIcon; private final Animatable mAnimatableIcon;
private Animator mIconAnimator;
private boolean mAnimationTriggered; private boolean mAnimationTriggered;
private AnimatorListenerAdapter mJankMonitoringListener; 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); 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 @Override
@@ -282,83 +309,68 @@ public class SplashscreenIconDrawableFactory {
} }
@Override @Override
public boolean prepareAnimate(long duration, Runnable startListener) { public void prepareAnimate(long duration, LongConsumer startListener) {
mAnimatableIcon = (Animatable) mForegroundDrawable; stopAnimation();
mIconAnimator = ValueAnimator.ofInt(0, 1); mDuration = duration;
mIconAnimator.setDuration(duration); mStartListener = startListener;
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();
}
}
@Override private void startAnimation() {
public void onAnimationEnd(Animator animation) { if (mJankMonitoringListener != null) {
mAnimatableIcon.stop(); mJankMonitoringListener.onAnimationStart(null);
if (mJankMonitoringListener != null) { }
mJankMonitoringListener.onAnimationEnd(animation); 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 private void onAnimationEnd() {
public void onAnimationCancel(Animator animation) { mAnimatableIcon.stop();
mAnimatableIcon.stop(); if (mJankMonitoringListener != null) {
if (mJankMonitoringListener != null) { mJankMonitoringListener.onAnimationEnd(null);
mJankMonitoringListener.onAnimationCancel(animation); }
} mStartListener = null;
} mRunning = false;
@Override
public void onAnimationRepeat(Animator animation) {
// do not repeat
mAnimatableIcon.stop();
}
});
return true;
} }
@Override @Override
public void stopAnimation() { public void stopAnimation() {
if (mIconAnimator != null && mIconAnimator.isRunning()) { if (mRunning) {
mIconAnimator.end(); mSplashScreenExecutor.removeCallbacks(this::stopAnimation);
onAnimationEnd();
mJankMonitoringListener = null; 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() { private void ensureAnimationStarted() {
if (mAnimationTriggered) { if (mAnimationTriggered) {
return; return;
} }
if (mIconAnimator != null && !mIconAnimator.isRunning()) { if (!mRunning) {
mIconAnimator.start(); startAnimation();
} }
mAnimationTriggered = true; mAnimationTriggered = true;
} }

View File

@@ -148,7 +148,8 @@ public class StartingSurfaceDrawer {
mContext = context; mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor; mSplashScreenExecutor = splashScreenExecutor;
mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool); mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool,
mSplashScreenExecutor);
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance()); mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
mWindowManagerGlobal = WindowManagerGlobal.getInstance(); mWindowManagerGlobal = WindowManagerGlobal.getInstance();
mDisplayManager.getDisplay(DEFAULT_DISPLAY); mDisplayManager.getDisplay(DEFAULT_DISPLAY);