Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e58dd9dd authored by wilsonshih's avatar wilsonshih Committed by Wei Sheng Shih
Browse files

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
parent 6310f4d5
Loading
Loading
Loading
Loading
+53 −4
Original line number Diff line number Diff line
@@ -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);
            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();
        }
    }
}
+24 −1
Original line number Diff line number Diff line
@@ -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) {
+52 −0
Original line number Diff line number Diff line
@@ -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();