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

Commit ed10f0ab authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Add an extra colored layer when animating launches (1/2)

This CL adds an extra layer with the same color as the app background
color when animating an app launch (e.g. from notification, QS, media,
etc). This allows to avoid a "flash effect" that could be caused when
the background color of the expanding view is bright, which is often the
case for QS tiles.

See b/188185381#comment5 for before/after videos.

Bug: 186189658
Test: Long press a quick settings tile
Change-Id: Ifc0cda3b03795549ddd7c333a9302e7ea298ac6a
parent 56fb52b0
Loading
Loading
Loading
Loading
+17 −11
Original line number Diff line number Diff line
@@ -135,7 +135,7 @@ public class SplashscreenContentDrawer {
                com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
    }

    private int getSystemBGColor() {
    private static int getSystemBGColor() {
        final Context systemContext = ActivityThread.currentApplication();
        if (systemContext == null) {
            Slog.e(TAG, "System context does not exist!");
@@ -145,17 +145,18 @@ public class SplashscreenContentDrawer {
        return res.getColor(com.android.wm.shell.R.color.splash_window_background_default);
    }

    private Drawable createDefaultBackgroundDrawable() {
    private static Drawable createDefaultBackgroundDrawable() {
        return new ColorDrawable(getSystemBGColor());
    }

    private @ColorInt int peekWindowBGColor(Context context) {
    /** Extract the window background color from {@code attrs}. */
    public static int peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs) {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
        final Drawable themeBGDrawable;
        if (mTmpAttrs.mWindowBgColor != 0) {
            themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
        } else if (mTmpAttrs.mWindowBgResId != 0) {
            themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
        if (attrs.mWindowBgColor != 0) {
            themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
        } else if (attrs.mWindowBgResId != 0) {
            themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
        } else {
            themeBGDrawable = createDefaultBackgroundDrawable();
            Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
@@ -165,7 +166,7 @@ public class SplashscreenContentDrawer {
        return estimatedWindowBGColor;
    }

    private int estimateWindowBGColor(Drawable themeBGDrawable) {
    private static int estimateWindowBGColor(Drawable themeBGDrawable) {
        final DrawableColorTester themeBGTester =
                new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
        if (themeBGTester.nonTransparentRatio() == 0) {
@@ -183,7 +184,7 @@ public class SplashscreenContentDrawer {

        getWindowAttrs(context, mTmpAttrs);
        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
        final int themeBGColor = peekWindowBGColor(context);
        final int themeBGColor = peekWindowBGColor(context, this.mTmpAttrs);
        // TODO (b/173975965) Tracking the performance on improved splash screen.
        return builder
                .setContext(context)
@@ -193,7 +194,11 @@ public class SplashscreenContentDrawer {
                .build();
    }

    private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
    /**
     * Get the {@link SplashScreenWindowAttrs} from {@code context} and fill them into
     * {@code attrs}.
     */
    public static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
        final TypedArray typedArray = context.obtainStyledAttributes(
                com.android.internal.R.styleable.Window);
        attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
@@ -216,7 +221,8 @@ public class SplashscreenContentDrawer {
        }
    }

    private static class SplashScreenWindowAttrs {
    /** The configuration of the splash screen window. */
    public static class SplashScreenWindowAttrs {
        private int mWindowBgResId = 0;
        private int mWindowBgColor = Color.TRANSPARENT;
        private Drawable mReplaceIcon = null;
+1 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ android_library {

    static_libs: [
        "PluginCoreLib",
        "WindowManager-Shell",
    ],

    manifest: "AndroidManifest.xml",
+118 −16
Original line number Diff line number Diff line
@@ -5,13 +5,18 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.app.AppGlobals
import android.app.PendingIntent
import android.content.Context
import android.graphics.Matrix
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.GradientDrawable
import android.os.Looper
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import android.util.MathUtils
import android.view.IRemoteAnimationFinishedCallback
@@ -26,6 +31,8 @@ import android.view.animation.AnimationUtils
import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer.SplashScreenWindowAttrs
import kotlin.math.roundToInt

/**
@@ -37,9 +44,9 @@ class ActivityLaunchAnimator(context: Context) {

    companion object {
        const val ANIMATION_DURATION = 500L
        const val ANIMATION_DURATION_FADE_OUT_CONTENT = 183L
        const val ANIMATION_DURATION_FADE_IN_WINDOW = 217L
        const val ANIMATION_DELAY_FADE_IN_WINDOW = 167L
        private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
        private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
        private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
        private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
        private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
        private const val ANIMATION_DELAY_NAV_FADE_IN =
@@ -51,6 +58,8 @@ class ActivityLaunchAnimator(context: Context) {
        private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
        private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)

        private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)

        /**
         * Given the [linearProgress] of a launch animation, return the linear progress of the
         * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
@@ -65,6 +74,8 @@ class ActivityLaunchAnimator(context: Context) {
        }
    }

    private val packageManager = AppGlobals.getPackageManager()

    /** The interpolator used for the width, height, Y position and corner radius. */
    private val animationInterpolator = AnimationUtils.loadInterpolator(context,
            R.interpolator.launch_animation_interpolator_y)
@@ -73,6 +84,8 @@ class ActivityLaunchAnimator(context: Context) {
    private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
            R.interpolator.launch_animation_interpolator_x)

    private val cornerRadii = FloatArray(8)

    /**
     * Start an intent and animate the opening window. The intent will be started by running
     * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
@@ -260,10 +273,7 @@ class ActivityLaunchAnimator(context: Context) {
        var right: Int,

        var topCornerRadius: Float = 0f,
        var bottomCornerRadius: Float = 0f,

        var contentAlpha: Float = 1f,
        var backgroundAlpha: Float = 1f
        var bottomCornerRadius: Float = 0f
    ) {
        private val startTop = top
        private val startBottom = bottom
@@ -303,6 +313,9 @@ class ActivityLaunchAnimator(context: Context) {

        val centerY: Float
            get() = top + height / 2f

        /** Whether the expanded view should be visible or hidden. */
        var visible: Boolean = true
    }

    @VisibleForTesting
@@ -425,22 +438,39 @@ class ActivityLaunchAnimator(context: Context) {
                0f
            }

            // We add an extra layer with the same color as the app splash screen background color,
            // which is usually the same color of the app background. We first fade in this layer
            // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
            // launch container and reveal the opening window.
            val windowBackgroundColor = extractSplashScreenBackgroundColor(window)
            val windowBackgroundLayer = GradientDrawable().apply {
                setColor(windowBackgroundColor)
                alpha = 0
            }

            // Update state.
            val animator = ValueAnimator.ofFloat(0f, 1f)
            this.animator = animator
            animator.duration = ANIMATION_DURATION
            animator.interpolator = Interpolators.LINEAR

            val launchContainerOverlay = launchContainer.overlay
            animator.addListener(object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
                    Log.d(TAG, "Animation started")
                    controller.onLaunchAnimationStart(isExpandingFullyAbove)

                    // Add the drawable to the launch container overlay. Overlays always draw
                    // drawables after views, so we know that it will be drawn above any view added
                    // by the controller.
                    launchContainerOverlay.add(windowBackgroundLayer)
                }

                override fun onAnimationEnd(animation: Animator?) {
                    Log.d(TAG, "Animation ended")
                    invokeCallback(iCallback)
                    controller.onLaunchAnimationEnd(isExpandingFullyAbove)
                    launchContainerOverlay.remove(windowBackgroundLayer)
                }
            })

@@ -464,24 +494,61 @@ class ActivityLaunchAnimator(context: Context) {
                state.bottomCornerRadius =
                    MathUtils.lerp(startBottomCornerRadius, endRadius, progress)

                val contentAlphaProgress = getProgress(linearProgress, 0,
                        ANIMATION_DURATION_FADE_OUT_CONTENT)
                state.contentAlpha =
                        1 - CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress)

                val backgroundAlphaProgress = getProgress(linearProgress,
                        ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
                state.backgroundAlpha =
                        1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress)
                // The expanding view can/should be hidden once it is completely coverred by the
                // windowBackgroundLayer.
                state.visible =
                        getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1

                applyStateToWindow(window, state)
                applyStateToWindowBackgroundLayer(windowBackgroundLayer, state, linearProgress)
                navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }

                // If we started expanding the view, we make it 1 pixel smaller on all sides to
                // avoid artefacts on the corners caused by anti-aliasing of the view background and
                // the window background layer.
                if (state.top != startTop && state.left != startLeft &&
                        state.bottom != startBottom && state.right != startRight) {
                    state.top += 1
                    state.left += 1
                    state.right -= 1
                    state.bottom -= 1
                }
                controller.onLaunchAnimationProgress(state, progress, linearProgress)
            }

            animator.start()
        }

        /** Extract the background color of the app splash screen. */
        private fun extractSplashScreenBackgroundColor(window: RemoteAnimationTarget): Int {
            val taskInfo = window.taskInfo
            val windowPackage = taskInfo.topActivity.packageName
            val userId = taskInfo.userId
            val windowContext = context.createPackageContextAsUser(
                    windowPackage, Context.CONTEXT_RESTRICTED, UserHandle.of(userId))
            val activityInfo = taskInfo.topActivityInfo
            val splashScreenThemeName = packageManager.getSplashScreenTheme(windowPackage, userId)
            val splashScreenThemeId = if (splashScreenThemeName != null) {
                windowContext.resources.getIdentifier(splashScreenThemeName, null, null)
            } else {
                0
            }

            val themeResId = when {
                splashScreenThemeId != 0 -> splashScreenThemeId
                activityInfo.themeResource != 0 -> activityInfo.themeResource
                else -> com.android.internal.R.style.Theme_DeviceDefault_DayNight
            }

            if (themeResId != windowContext.themeResId) {
                windowContext.setTheme(themeResId)
            }

            val windowAttrs = SplashScreenWindowAttrs()
            SplashscreenContentDrawer.getWindowAttrs(windowContext, windowAttrs)
            return SplashscreenContentDrawer.peekWindowBGColor(windowContext, windowAttrs)
        }

        private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
            val screenBounds = window.screenSpaceBounds
            val centerX = (screenBounds.left + screenBounds.right) / 2f
@@ -533,6 +600,41 @@ class ActivityLaunchAnimator(context: Context) {
            transactionApplier.scheduleApply(params)
        }

        private fun applyStateToWindowBackgroundLayer(
            drawable: GradientDrawable,
            state: State,
            linearProgress: Float
        ) {
            // Update position.
            drawable.setBounds(state.left, state.top, state.right, state.bottom)

            // Update radius.
            cornerRadii[0] = state.topCornerRadius
            cornerRadii[1] = state.topCornerRadius
            cornerRadii[2] = state.topCornerRadius
            cornerRadii[3] = state.topCornerRadius
            cornerRadii[4] = state.bottomCornerRadius
            cornerRadii[5] = state.bottomCornerRadius
            cornerRadii[6] = state.bottomCornerRadius
            cornerRadii[7] = state.bottomCornerRadius
            drawable.cornerRadii = cornerRadii

            // We first fade in the background layer to hide the expanding view, then fade it out
            // with SRC mode to draw a hole punch in the status bar and reveal the opening window.
            val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
            if (fadeInProgress < 1) {
                val alpha = CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(fadeInProgress)
                drawable.alpha = (alpha * 0xFF).roundToInt()
                drawable.setXfermode(null)
            } else {
                val fadeOutProgress = getProgress(linearProgress,
                        ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
                val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
                drawable.alpha = (alpha * 0xFF).roundToInt()
                drawable.setXfermode(SRC_MODE)
            }
        }

        private fun applyStateToNavigationBar(
            navigationBar: RemoteAnimationTarget,
            state: State,
+15 −38
Original line number Diff line number Diff line
@@ -4,8 +4,6 @@ import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Matrix
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
@@ -111,9 +109,7 @@ open class GhostedViewLaunchAnimatorController(
    }

    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
        backgroundView = FrameLayout(launchContainer.context).apply {
            forceHasOverlappingRendering(false)
        }
        backgroundView = FrameLayout(launchContainer.context)
        launchContainerOverlay.add(backgroundView)

        // We wrap the ghosted view background and use it to draw the expandable background. Its
@@ -125,9 +121,7 @@ open class GhostedViewLaunchAnimatorController(

        // Create a ghost of the view that will be moving and fading out. This allows to fade out
        // the content before fading out the background.
        ghostView = GhostView.addGhost(ghostedView, launchContainer).apply {
            setLayerType(View.LAYER_TYPE_HARDWARE, null)
        }
        ghostView = GhostView.addGhost(ghostedView, launchContainer)

        val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
        matrix.getValues(initialGhostViewMatrixValues)
@@ -139,7 +133,18 @@ open class GhostedViewLaunchAnimatorController(
        linearProgress: Float
    ) {
        val ghostView = this.ghostView!!
        ghostView.alpha = state.contentAlpha
        val backgroundView = this.backgroundView!!

        if (!state.visible) {
            if (ghostView.visibility == View.VISIBLE) {
                // Making the ghost view invisible will make the ghosted view visible, so order is
                // important here.
                ghostView.visibility = View.INVISIBLE
                ghostedView.visibility = View.INVISIBLE
                backgroundView.visibility = View.INVISIBLE
            }
            return
        }

        val scale = min(state.widthRatio, state.heightRatio)
        ghostViewMatrix.setValues(initialGhostViewMatrixValues)
@@ -150,14 +155,12 @@ open class GhostedViewLaunchAnimatorController(
        )
        ghostView.animationMatrix = ghostViewMatrix

        val backgroundView = this.backgroundView!!
        backgroundView.top = state.top
        backgroundView.bottom = state.bottom
        backgroundView.left = state.left
        backgroundView.right = state.right

        val backgroundDrawable = backgroundDrawable!!
        backgroundDrawable.alpha = (0xFF * state.backgroundAlpha).toInt()
        backgroundDrawable.wrapped?.let {
            setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius)
        }
@@ -168,6 +171,7 @@ open class GhostedViewLaunchAnimatorController(

        GhostView.removeGhost(ghostedView)
        launchContainerOverlay.remove(backgroundView)
        ghostedView.visibility = View.VISIBLE
        ghostedView.invalidate()
    }

@@ -203,10 +207,6 @@ open class GhostedViewLaunchAnimatorController(
    }

    private class WrappedDrawable(val wrapped: Drawable?) : Drawable() {
        companion object {
            private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
        }

        private var currentAlpha = 0xFF
        private var previousBounds = Rect()

@@ -220,7 +220,6 @@ open class GhostedViewLaunchAnimatorController(

            wrapped.alpha = currentAlpha
            wrapped.bounds = bounds
            setXfermode(wrapped, SRC_MODE)
            applyBackgroundRadii()

            wrapped.draw(canvas)
@@ -230,7 +229,6 @@ open class GhostedViewLaunchAnimatorController(
            // background.
            wrapped.alpha = 0
            wrapped.bounds = previousBounds
            setXfermode(wrapped, null)
            restoreBackgroundRadii()
        }

@@ -257,27 +255,6 @@ open class GhostedViewLaunchAnimatorController(
            wrapped?.colorFilter = filter
        }

        private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) {
            if (background is InsetDrawable) {
                background.drawable?.let { setXfermode(it, mode) }
                return
            }

            if (background !is LayerDrawable) {
                background.setXfermode(mode)
                return
            }

            // We set the xfermode on the first layer that is not a mask. Most of the time it will
            // be the "background layer".
            for (i in 0 until background.numberOfLayers) {
                if (background.getId(i) != android.R.id.mask) {
                    background.getDrawable(i).setXfermode(mode)
                    break
                }
            }
        }

        fun setBackgroundRadius(topCornerRadius: Float, bottomCornerRadius: Float) {
            updateRadii(cornerRadii, topCornerRadius, bottomCornerRadius)
            invalidateSelf()
+9 −5
Original line number Diff line number Diff line
@@ -73,7 +73,6 @@ import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager;
@@ -2020,6 +2019,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
        if (params == null) {
            return;
        }

        if (!params.getVisible()) {
            if (getVisibility() == View.VISIBLE) {
                setVisibility(View.INVISIBLE);
            }
            return;
        }

        float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
                params.getProgress(0, 50));
        float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
@@ -2077,10 +2084,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
            contentView = mGuts;
        }
        if (expandAnimationRunning) {
            contentView.animate()
                    .alpha(0f)
                    .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_OUT_CONTENT)
                    .setInterpolator(ActivityLaunchAnimator.CONTENT_FADE_OUT_INTERPOLATOR);
            setAboveShelf(true);
            mExpandAnimationRunning = true;
            getViewState().cancelAnimations(this);
@@ -2088,6 +2091,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
        } else {
            mExpandAnimationRunning = false;
            setAboveShelf(isAboveShelf());
            setVisibility(View.VISIBLE);
            if (mGuts != null) {
                mGuts.setAlpha(1.0f);
            }
Loading