Merge "Estimate animation duration for AVD and AnimationDrawable" into tm-dev am: 172ea326de

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

Change-Id: Ifef73f174b6e71d1d237ee5d56c2b54cee73e354
This commit is contained in:
Wei Sheng Shih
2022-03-02 06:38:41 +00:00
committed by Automerger Merge Worker
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.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.

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 {
@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();

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);