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