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

Commit 3221600d authored by wilsonshih's avatar wilsonshih
Browse files

Support enable default splash screen reveal aninatmion

- Add a temp flag persist.debug.enable_reveal_animation so user can
enable reveal animation.
- Remove icon shift animation.
- For shift up animation, use AnimationAdapter to capture the surface
control and pass to Shell, then cancel the animation when
WindowState#removeIfPossible for starting window.
- Fix the flicker test fail issue caused from shift up animation, the
surface control should be release after the last frame was commit.
- Use SyncRtSurfaceTransactionApplier to synchronize the shift-up
animation, and use vsyncId to ensure the transaction doesn't applyied
too early.
- Do not play reveal animation for empty splash screen.

Test: atest SplashscreenTests StartingSurfaceDrawerTests
Bug: 183004107
Bug: 182815506
Bug: 184830058
Change-Id: I4fc8456633b3f4ecd69b168e0222915945210426
parent 3ed02ffe
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ public final class SplashScreenView extends FrameLayout {
    private static final boolean DEBUG = false;

    private boolean mNotCopyable;
    private boolean mRevealAnimationSupported = true;
    private int mInitBackgroundColor;
    private int mInitIconBackgroundColor;
    private View mIconView;
@@ -263,6 +264,25 @@ public final class SplashScreenView extends FrameLayout {
        return !mNotCopyable;
    }

    /**
     * If set to true, indicates to the system that this view can be dismissed by playing the
     * Reveal animation.
     * <p>
     * If the exit animation is handled by the client, the animation won't be played anyway.
     * @hide
     */
    public void setRevealAnimationSupported(boolean support) {
        mRevealAnimationSupported = support;
    }

    /**
     * Whether this view support reveal animation.
     * @hide
     */
    public boolean isRevealAnimationSupported() {
        return mRevealAnimationSupported;
    }

    /**
     * Returns the duration of the icon animation if icon is animatable.
     *
+0 −3
Original line number Diff line number Diff line
@@ -52,9 +52,6 @@
     when the PIP menu is shown in center. -->
    <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>

    <!-- Animation duration when exit starting window: icon going away -->
    <integer name="starting_window_icon_exit_anim_duration">166</integer>

    <!-- Animation duration when exit starting window: reveal app -->
    <integer name="starting_window_app_reveal_anim_duration">333</integer>
</resources>
+51 −86
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.wm.shell.startingsurface;

import static android.view.Choreographer.CALLBACK_COMMIT;
import static android.view.View.GONE;

import android.animation.Animator;
@@ -29,13 +30,12 @@ import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.Shader;
import android.util.Slog;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.animation.Transformation;
@@ -53,51 +53,36 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
    private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
    private static final String TAG = StartingSurfaceDrawer.TAG;

    private static final Interpolator ICON_EXIT_INTERPOLATOR = new PathInterpolator(1f, 0f, 1f, 1f);
    private static final Interpolator APP_EXIT_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);

    private static final int EXTRA_REVEAL_DELAY = 133;
    private final Matrix mTmpTransform = new Matrix();
    private final float[] mTmpFloat9 = new float[9];
    private SurfaceControl mFirstWindowSurface;
    private final SurfaceControl mFirstWindowSurface;
    private final Rect mFirstWindowFrame = new Rect();
    private final SplashScreenView mSplashScreenView;
    private final int mMainWindowShiftLength;
    private final int mIconShiftLength;
    private final int mAppDuration;
    private final int mIconDuration;
    private final TransactionPool mTransactionPool;

    private ValueAnimator mMainAnimator;
    private Animation mShiftUpAnimation;
    private AnimationSet mIconAnimationSet;
    private ShiftUpAnimation mShiftUpAnimation;
    private Runnable mFinishCallback;

    SplashScreenExitAnimation(SplashScreenView view, SurfaceControl leash, Rect frame,
            int appDuration, int iconDuration, int mainWindowShiftLength, int iconShiftLength,
            TransactionPool pool, Runnable handleFinish) {
            int appDuration, int mainWindowShiftLength, TransactionPool pool,
            Runnable handleFinish) {
        mSplashScreenView = view;
        mFirstWindowSurface = leash;
        if (frame != null) {
            mFirstWindowFrame.set(frame);
        }
        mAppDuration = appDuration;
        mIconDuration = iconDuration;
        mMainWindowShiftLength = mainWindowShiftLength;
        mIconShiftLength = iconShiftLength;
        mFinishCallback = handleFinish;
        mTransactionPool = pool;
    }

    void prepareAnimations() {
        prepareRevealAnimation();
        prepareShiftAnimation();
    }

    void startAnimations() {
        if (mIconAnimationSet != null) {
            mIconAnimationSet.start();
        }
        prepareRevealAnimation();
        if (mMainAnimator != null) {
            mMainAnimator.start();
        }
@@ -114,8 +99,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
        mMainAnimator.setInterpolator(APP_EXIT_INTERPOLATOR);
        mMainAnimator.addListener(this);

        final int startDelay = mIconDuration + EXTRA_REVEAL_DELAY;
        final float transparentRatio = 0.95f;
        final float transparentRatio = 0.8f;
        final int globalHeight = mSplashScreenView.getHeight();
        final int verticalCircleCenter = 0;
        final int finalVerticalLength = globalHeight - verticalCircleCenter;
@@ -130,9 +114,8 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
        final float[] stops = {0f, transparentRatio, 1f};
        radialVanishAnimation.setRadialPaintParam(colors, stops);
        radialVanishAnimation.setReady();
        mMainAnimator.setStartDelay(startDelay);

        if (mFirstWindowSurface != null) {
        if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
            // shift up main window
            View occludeHoleView = new View(mSplashScreenView.getContext());
            if (DEBUG_EXIT_ANIMATION_BLEND) {
@@ -144,59 +127,16 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
                    WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
            mSplashScreenView.addView(occludeHoleView, params);

            mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength);
            mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
            mShiftUpAnimation.setDuration(mAppDuration);
            mShiftUpAnimation.setInterpolator(APP_EXIT_INTERPOLATOR);
            mShiftUpAnimation.setStartOffset(startDelay);

            occludeHoleView.setAnimation(mShiftUpAnimation);
        }
    }

    // shift down icon and branding view
    private void prepareShiftAnimation() {
        final View iconView = mSplashScreenView.getIconView();
        if (iconView == null) {
            return;
        }
        if (mIconShiftLength > 0) {
            mIconAnimationSet = new AnimationSet(true /* shareInterpolator */);
            if (DEBUG_EXIT_ANIMATION) {
                Slog.v(TAG, "first exit animation, shift length: " + mIconShiftLength);
            }
            mIconAnimationSet.addAnimation(new TranslateYAnimation(0, mIconShiftLength));
            mIconAnimationSet.addAnimation(new AlphaAnimation(1, 0));
            mIconAnimationSet.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    if (DEBUG_EXIT_ANIMATION) {
                        Slog.v(TAG, "first exit animation finished");
                    }
                    iconView.post(() -> iconView.setVisibility(GONE));
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                    // ignore
                }
            });
            mIconAnimationSet.setDuration(mIconDuration);
            mIconAnimationSet.setInterpolator(ICON_EXIT_INTERPOLATOR);
            iconView.setAnimation(mIconAnimationSet);
            final View brandingView = mSplashScreenView.getBrandingView();
            if (brandingView != null) {
                brandingView.setAnimation(mIconAnimationSet);
            }
        }
    }

    private static class RadialVanishAnimation extends View {
        private SplashScreenView mView;
        private final SplashScreenView mView;
        private int mInitRadius;
        private int mFinishRadius;
        private boolean mReady;
@@ -217,7 +157,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
                mVanishMatrix.setScale(scale, scale);
                mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
                mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
                mView.postInvalidate();
                postInvalidate();
            });
            mView.addView(this);
        }
@@ -262,28 +202,57 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
    }

    private final class ShiftUpAnimation extends TranslateYAnimation {
        ShiftUpAnimation(float fromYDelta, float toYDelta) {
        final SyncRtSurfaceTransactionApplier mApplier;
        ShiftUpAnimation(float fromYDelta, float toYDelta, View targetView) {
            super(fromYDelta, toYDelta);
            mApplier = new SyncRtSurfaceTransactionApplier(targetView);
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);

            if (mFirstWindowSurface == null) {
            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
                return;
            }
            mTmpTransform.set(t.getMatrix());

            // set the vsyncId to ensure the transaction doesn't get applied too early.
            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
            tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
            mTmpTransform.postTranslate(mFirstWindowFrame.left,
                    mFirstWindowFrame.top + mMainWindowShiftLength);
            tx.setMatrix(mFirstWindowSurface, mTmpTransform, mTmpFloat9);
            // TODO set the vsyncId to ensure the transaction doesn't get applied too early.
            //  Additionally, do you want to have this synchronized with your view animations?
            //  If so, you'll need to use SyncRtSurfaceTransactionApplier
            tx.apply();

            SyncRtSurfaceTransactionApplier.SurfaceParams
                    params = new SyncRtSurfaceTransactionApplier.SurfaceParams
                    .Builder(mFirstWindowSurface)
                    .withMatrix(mTmpTransform)
                    .withMergeTransaction(tx)
                    .build();
            mApplier.scheduleApply(params);

            mTransactionPool.release(tx);
        }

        void finish() {
            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
                return;
            }
            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
            tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());

            SyncRtSurfaceTransactionApplier.SurfaceParams
                    params = new SyncRtSurfaceTransactionApplier.SurfaceParams
                    .Builder(mFirstWindowSurface)
                    .withWindowCrop(null)
                    .withMergeTransaction(tx)
                    .build();
            mApplier.scheduleApply(params);
            mTransactionPool.release(tx);

            Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
                    mFirstWindowSurface::release, null);
        }
    }

    private void reset() {
@@ -297,12 +266,8 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
                mFinishCallback = null;
            }
        });
        if (mFirstWindowSurface != null) {
            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
            tx.setWindowCrop(mFirstWindowSurface, null);
            tx.apply();
            mFirstWindowSurface.release();
            mFirstWindowSurface = null;
        if (mShiftUpAnimation != null) {
            mShiftUpAnimation.finish();
        }
    }

+4 −14
Original line number Diff line number Diff line
@@ -76,20 +76,15 @@ public class SplashscreenContentDrawer {
    private int mBrandingImageWidth;
    private int mBrandingImageHeight;
    private final int mAppRevealDuration;
    private final int mIconExitDuration;
    private int mMainWindowShiftLength;
    private int mIconNormalExitDistance;
    private int mIconEarlyExitDistance;
    private final TransactionPool mTransactionPool;
    private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
    private final Handler mSplashscreenWorkerHandler;

    SplashscreenContentDrawer(Context context, int iconExitAnimDuration, int appRevealAnimDuration,
            TransactionPool pool) {
    SplashscreenContentDrawer(Context context, int appRevealAnimDuration, TransactionPool pool) {
        mContext = context;
        mIconProvider = new IconProvider(context);
        mAppRevealDuration = appRevealAnimDuration;
        mIconExitDuration = iconExitAnimDuration;
        mTransactionPool = pool;

        // Initialize Splashscreen worker thread
@@ -144,10 +139,6 @@ public class SplashscreenContentDrawer {
                com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
        mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
        mIconNormalExitDistance = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.starting_surface_normal_exit_icon_distance);
        mIconEarlyExitDistance = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.starting_surface_early_exit_icon_distance);
    }

    private int getSystemBGColor() {
@@ -428,6 +419,7 @@ public class SplashscreenContentDrawer {
            }
            if (mEmptyView) {
                splashScreenView.setNotCopyable();
                splashScreenView.setRevealAnimationSupported(false);
            }
            splashScreenView.makeSystemUIColorsTransparent();
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -724,12 +716,10 @@ public class SplashscreenContentDrawer {
     * Create and play the default exit animation for splash screen view.
     */
    void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
            Rect frame, boolean isEarlyExit, Runnable finishCallback) {
            Rect frame, Runnable finishCallback) {
        final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(view, leash,
                frame, mAppRevealDuration, mIconExitDuration, mMainWindowShiftLength,
                isEarlyExit ? mIconEarlyExitDistance : mIconNormalExitDistance, mTransactionPool,
                frame, mAppRevealDuration, mMainWindowShiftLength, mTransactionPool,
                finishCallback);
        animation.prepareAnimations();
        animation.startAnimations();
    }
}
+20 −23
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
@@ -106,6 +106,8 @@ public class StartingSurfaceDrawer {
    private final SplashscreenContentDrawer mSplashscreenContentDrawer;
    private Choreographer mChoreographer;

    private static final boolean DEBUG_ENABLE_REVEAL_ANIMATION =
            SystemProperties.getBoolean("persist.debug.enable_reveal_animation", false);
    /**
     * @param splashScreenExecutor The thread used to control add and remove starting window.
     */
@@ -114,12 +116,10 @@ public class StartingSurfaceDrawer {
        mContext = context;
        mDisplayManager = mContext.getSystemService(DisplayManager.class);
        mSplashScreenExecutor = splashScreenExecutor;
        final int iconExitAnimDuration = context.getResources().getInteger(
                com.android.wm.shell.R.integer.starting_window_icon_exit_anim_duration);
        final int appRevealAnimDuration = context.getResources().getInteger(
                com.android.wm.shell.R.integer.starting_window_app_reveal_anim_duration);
        mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconExitAnimDuration,
                appRevealAnimDuration, pool);
        mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, appRevealAnimDuration,
                pool);
        mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
    }

@@ -471,20 +471,24 @@ public class StartingSurfaceDrawer {
                if (DEBUG_SPLASH_SCREEN) {
                    Slog.v(TAG, "Removing splash screen window for task: " + taskId);
                }
                if (record.mContentView != null) {
                    if (leash != null || playRevealAnimation) {
                if (record.mContentView != null
                        && record.mContentView.isRevealAnimationSupported()) {
                    if (playRevealAnimation) {
                        if (DEBUG_ENABLE_REVEAL_ANIMATION) {
                            mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
                                leash, frame, record.isEarlyExit(),
                                    leash, frame,
                                    () -> removeWindowInner(record.mDecorView, true));
                        } else {
                        // TODO(183004107) Always hide decorView when playRevealAnimation is enabled
                        //  from TaskOrganizerController#removeStartingWindow
                        // the SplashScreenView has been copied to client, skip default exit
                        // animation
                            // using the default exit animation from framework
                            removeWindowInner(record.mDecorView, false);
                        }
                    } else {
                    // no animation will be applied
                        // the SplashScreenView has been copied to client, hide the view to skip
                        // default exit animation
                        removeWindowInner(record.mDecorView, true);
                    }
                } else {
                    // this is a blank splash screen, don't apply reveal animation
                    removeWindowInner(record.mDecorView, false);
                }
            }
@@ -518,12 +522,10 @@ public class StartingSurfaceDrawer {
     * Record the view or surface for a starting window.
     */
    private static class StartingWindowRecord {
        private static final long EARLY_START_MINIMUM_TIME_MS = 250;
        private final View mDecorView;
        private final TaskSnapshotWindow mTaskSnapshotWindow;
        private SplashScreenView mContentView;
        private boolean mSetSplashScreen;
        private long mContentCreateTime;

        StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) {
            mDecorView = decorView;
@@ -535,12 +537,7 @@ public class StartingSurfaceDrawer {
                return;
            }
            mContentView = splashScreenView;
            mContentCreateTime = SystemClock.uptimeMillis();
            mSetSplashScreen = true;
        }

        boolean isEarlyExit() {
            return SystemClock.uptimeMillis() - mContentCreateTime < EARLY_START_MINIMUM_TIME_MS;
        }
    }
}
Loading