From e58dd9dd51ad60e04767d89d5dc6dc9d29e7b189 Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Tue, 28 Dec 2021 18:46:00 +0800 Subject: [PATCH] Avoid cutting off AVD on splash screen. Extending the splash screen on screen duration so the icon won't make user feel flicker even if the app was drawn very quickly. Bug: 205906929 Test: atest SplashscreenTests StartingSurfaceDrawerTests Change-Id: Ic006e0f369897b13503646063271a0644c77a8a6 --- .../SplashscreenContentDrawer.java | 57 +++++++++++++++++-- .../StartingSurfaceDrawer.java | 25 +++++++- .../StartingSurfaceDrawerTests.java | 52 +++++++++++++++++ 3 files changed, 129 insertions(+), 5 deletions(-) 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 e255e44503fb3..025bcad079556 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 @@ -22,6 +22,9 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLAS import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION; + import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; @@ -46,6 +49,7 @@ import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; +import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.util.ArrayMap; @@ -322,6 +326,33 @@ public class SplashscreenContentDrawer { private int mAnimationDuration = 0; } + /** + * Get an optimal animation duration to keep the splash screen from showing. + * + * @param animationDuration The animation duration defined from app. + * @param appReadyDuration The real duration from the starting the app to the first app window + * drawn. + */ + @VisibleForTesting + static long getShowingDuration(long animationDuration, long appReadyDuration) { + if (animationDuration <= appReadyDuration) { + // app window ready took longer time than animation, it can be removed ASAP. + return appReadyDuration; + } + if (appReadyDuration < MAX_ANIMATION_DURATION) { + if (animationDuration > MAX_ANIMATION_DURATION + || appReadyDuration < MINIMAL_ANIMATION_DURATION) { + // animation is too long or too short, cut off with minimal duration + return MINIMAL_ANIMATION_DURATION; + } + // animation is longer than dOpt but shorter than max, allow it to play till finish + return MAX_ANIMATION_DURATION; + } + // the shortest duration is longer than dMax, cut off no matter how long the animation + // will be. + return appReadyDuration; + } + private class StartingWindowViewBuilder { private final Context mContext; private final ActivityInfo mActivityInfo; @@ -977,9 +1008,27 @@ public class SplashscreenContentDrawer { * Create and play the default exit animation for splash screen view. */ void applyExitAnimation(SplashScreenView view, SurfaceControl leash, - Rect frame, Runnable finishCallback) { - final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext, view, - leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback); - animation.startAnimations(); + Rect frame, Runnable finishCallback, long createTime) { + final Runnable playAnimation = () -> { + final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext, + view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback); + animation.startAnimations(); + }; + if (view.getIconView() == null) { + playAnimation.run(); + return; + } + final long appReadyDuration = SystemClock.uptimeMillis() - createTime; + final long animDuration = view.getIconAnimationDuration() != null + ? view.getIconAnimationDuration().toMillis() : 0; + final long minimumShowingDuration = getShowingDuration(animDuration, appReadyDuration); + final long delayed = minimumShowingDuration - appReadyDuration; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "applyExitAnimation delayed: %s", delayed); + if (delayed > 0) { + view.postDelayed(playAnimation, delayed); + } else { + playAnimation.run(); + } } } 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 9a966b8f93053..34426991a459b 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 @@ -41,6 +41,7 @@ import android.hardware.display.DisplayManager; import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.util.Slog; @@ -120,6 +121,25 @@ public class StartingSurfaceDrawer { private StartingSurface.SysuiProxy mSysuiProxy; private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo(); + /** + * The minimum duration during which the splash screen is shown when the splash screen icon is + * animated. + */ + static final long MINIMAL_ANIMATION_DURATION = 400L; + + /** + * Allow the icon style splash screen to be displayed for longer to give time for the animation + * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly + * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration. + */ + static final long TIME_WINDOW_DURATION = 100L; + + /** + * The maximum duration during which the splash screen will be shown if the application is ready + * to show before the icon animation finishes. + */ + static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION; + /** * @param splashScreenExecutor The thread used to control add and remove starting window. */ @@ -593,7 +613,8 @@ public class StartingSurfaceDrawer { if (removalInfo.playRevealAnimation) { mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, removalInfo.windowAnimationLeash, removalInfo.mainFrame, - () -> removeWindowInner(record.mDecorView, true)); + () -> removeWindowInner(record.mDecorView, true), + record.mCreateTime); } else { // the SplashScreenView has been copied to client, hide the view to skip // default exit animation @@ -641,6 +662,7 @@ public class StartingSurfaceDrawer { private boolean mSetSplashScreen; private @StartingWindowType int mSuggestType; private int mBGColor; + private final long mCreateTime; StartingWindowRecord(IBinder appToken, View decorView, TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) { @@ -651,6 +673,7 @@ public class StartingSurfaceDrawer { mBGColor = mTaskSnapshotWindow.getBackgroundColor(); } mSuggestType = suggestType; + mCreateTime = SystemClock.uptimeMillis(); } private void setSplashScreenView(SplashScreenView splashScreenView) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index d92b12e60780c..630d0d2c827cb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -24,6 +24,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -297,6 +299,56 @@ public class StartingSurfaceDrawerTests { assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0); } + @Test + public void testMinimumAnimationDuration() { + final long maxDuration = MAX_ANIMATION_DURATION; + final long minDuration = MINIMAL_ANIMATION_DURATION; + + final long shortDuration = minDuration - 1; + final long medianShortDuration = minDuration + 1; + final long medianLongDuration = maxDuration - 1; + final long longAppDuration = maxDuration + 1; + + // static icon + assertEquals(shortDuration, SplashscreenContentDrawer.getShowingDuration( + 0, shortDuration)); + // median launch + static icon + assertEquals(medianShortDuration, SplashscreenContentDrawer.getShowingDuration( + 0, medianShortDuration)); + // long launch + static icon + assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration( + 0, longAppDuration)); + + // fast launch + animatable icon + assertEquals(shortDuration, SplashscreenContentDrawer.getShowingDuration( + shortDuration, shortDuration)); + assertEquals(minDuration, SplashscreenContentDrawer.getShowingDuration( + medianShortDuration, shortDuration)); + assertEquals(minDuration, SplashscreenContentDrawer.getShowingDuration( + longAppDuration, shortDuration)); + + // median launch + animatable icon + assertEquals(medianShortDuration, SplashscreenContentDrawer.getShowingDuration( + shortDuration, medianShortDuration)); + assertEquals(medianShortDuration, SplashscreenContentDrawer.getShowingDuration( + medianShortDuration, medianShortDuration)); + assertEquals(minDuration, SplashscreenContentDrawer.getShowingDuration( + longAppDuration, medianShortDuration)); + // between min < max launch + animatable icon + assertEquals(medianLongDuration, SplashscreenContentDrawer.getShowingDuration( + medianShortDuration, medianLongDuration)); + assertEquals(maxDuration, SplashscreenContentDrawer.getShowingDuration( + medianLongDuration, medianShortDuration)); + + // long launch + animatable icon + assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration( + shortDuration, longAppDuration)); + assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration( + medianShortDuration, longAppDuration)); + assertEquals(longAppDuration, SplashscreenContentDrawer.getShowingDuration( + longAppDuration, longAppDuration)); + } + private StartingWindowInfo createWindowInfo(int taskId, int themeResId) { StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo();