Move ActivityLaunchAnimator in its own lib. (1/n)

Bug: 184121838
Test: Manual
Change-Id: Ib979fed2f59d9dbf5f0696edb5fcb4956600e6e0
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index b3aba22..f415da8 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -34,6 +34,7 @@
     static_libs: [
         "PluginCoreLib",
         "SystemUI-sensors",
+        "SystemUIAnimationLib",
     ],
 
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 055fe37..00bea8d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -19,7 +19,7 @@
 import android.content.Intent;
 import android.view.View;
 
-import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
 /**
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt
deleted file mode 100644
index 5af8dab..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt
+++ /dev/null
@@ -1,479 +0,0 @@
-package com.android.systemui.plugins.animation
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.app.ActivityManager
-import android.app.PendingIntent
-import android.graphics.Matrix
-import android.graphics.Rect
-import android.os.RemoteException
-import android.util.MathUtils
-import android.view.IRemoteAnimationFinishedCallback
-import android.view.IRemoteAnimationRunner
-import android.view.RemoteAnimationAdapter
-import android.view.RemoteAnimationTarget
-import android.view.SyncRtSurfaceTransactionApplier
-import android.view.View
-import android.view.WindowManager
-import android.view.animation.LinearInterpolator
-import android.view.animation.PathInterpolator
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.policy.ScreenDecorationsUtils
-import kotlin.math.roundToInt
-
-/**
- * A class that allows activities to be started in a seamless way from a view that is transforming
- * nicely into the starting window.
- */
-class ActivityLaunchAnimator {
-    companion object {
-        const val ANIMATION_DURATION = 400L
-        const val ANIMATION_DURATION_FADE_OUT_CONTENT = 67L
-        const val ANIMATION_DURATION_FADE_IN_WINDOW = 200L
-        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 =
-                ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
-        private const val LAUNCH_TIMEOUT = 1000L
-
-        // TODO(b/184121838): Use android.R.interpolator.fast_out_extra_slow_in instead.
-        // TODO(b/184121838): Move com.android.systemui.Interpolators in an animation library we can
-        // reuse here.
-        private val ANIMATION_INTERPOLATOR = PathInterpolator(0.4f, 0f, 0.2f, 1f)
-        private val LINEAR_INTERPOLATOR = LinearInterpolator()
-        private val ALPHA_IN_INTERPOLATOR = PathInterpolator(0.4f, 0f, 1f, 1f)
-        private val ALPHA_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.8f, 1f)
-        private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
-        private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
-
-        /**
-         * 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].
-         */
-        @JvmStatic
-        fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
-            return MathUtils.constrain(
-                    (linearProgress * ANIMATION_DURATION - delay) / duration,
-                    0.0f,
-                    1.0f
-            )
-        }
-    }
-
-    /**
-     * 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
-     * result. [controller] is responsible from animating the view from which the intent was started
-     * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
-     * opening.
-     *
-     * If [controller] is null, then the intent will be started and no animation will run.
-     *
-     * This method will throw any exception thrown by [intentStarter].
-     */
-    inline fun startIntentWithAnimation(
-        controller: Controller?,
-        intentStarter: (RemoteAnimationAdapter?) -> Int
-    ) {
-        if (controller == null) {
-            intentStarter(null)
-            return
-        }
-
-        val runner = Runner(controller)
-        val animationAdapter = RemoteAnimationAdapter(
-            runner,
-            ANIMATION_DURATION,
-            ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
-        )
-        val launchResult = intentStarter(animationAdapter)
-        val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT ||
-            launchResult == ActivityManager.START_SUCCESS
-        runner.context.mainExecutor.execute { controller.onIntentStarted(willAnimate) }
-
-        // If we expect an animation, post a timeout to cancel it in case the remote animation is
-        // never started.
-        if (willAnimate) {
-            runner.postTimeout()
-        }
-    }
-
-    /**
-     * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
-     * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
-     * for Java caller starting a [PendingIntent].
-     */
-    @Throws(PendingIntent.CanceledException::class)
-    fun startPendingIntentWithAnimation(
-        controller: Controller?,
-        intentStarter: PendingIntentStarter
-    ) {
-        startIntentWithAnimation(controller) { intentStarter.startPendingIntent(it) }
-    }
-
-    interface PendingIntentStarter {
-        /**
-         * Start a pending intent using the provided [animationAdapter] and return the launch
-         * result.
-         */
-        @Throws(PendingIntent.CanceledException::class)
-        fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
-    }
-
-    /**
-     * A controller that takes care of applying the animation to an expanding view.
-     *
-     * Note that all callbacks (onXXX methods) are all called on the main thread.
-     */
-    interface Controller {
-        companion object {
-            /**
-             * Return a [Controller] that will animate and expand [view] into the opening window.
-             *
-             * Important: The view must be attached to the window when calling this function and
-             * during the animation.
-             */
-            @JvmStatic
-            fun fromView(view: View): Controller = GhostedViewLaunchAnimatorController(view)
-        }
-
-        /**
-         * Return the root [View] that contains the view that started the intent and will be
-         * animating together with the window.
-         *
-         * This view will be used to:
-         *  - Get the associated [Context].
-         *  - Compute whether we are expanding fully above the current window.
-         *  - Apply surface transactions in sync with RenderThread.
-         */
-        fun getRootView(): View
-
-        /**
-         * Return the [State] of the view that will be animated. We will animate from this state to
-         * the final window state.
-         *
-         * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
-         * animation.
-         */
-        fun createAnimatorState(): State
-
-        /**
-         * The intent was started. If [willAnimate] is false, nothing else will happen and the
-         * animation will not be started.
-         */
-        fun onIntentStarted(willAnimate: Boolean) {}
-
-        /**
-         * The animation started. This is typically used to initialize any additional resource
-         * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
-         * fully above the [root view][getRootView].
-         */
-        fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
-
-        /** The animation made progress and the expandable view [state] should be updated. */
-        fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
-
-        /**
-         * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
-         * called previously. This is typically used to clean up the resources initialized when the
-         * animation was started.
-         */
-        fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
-
-        /**
-         * The animation was cancelled remotely. Note that [onLaunchAnimationEnd] will still be
-         * called after this if the animation was already started, i.e. if [onLaunchAnimationStart]
-         * was called before the cancellation.
-         */
-        fun onLaunchAnimationCancelled() {}
-
-        /**
-         * The remote animation was not started within the expected time. It timed out and will
-         * never [start][onLaunchAnimationStart].
-         */
-        fun onLaunchAnimationTimedOut() {}
-
-        /**
-         * The animation was aborted because the opening window was not found. It will never
-         * [start][onLaunchAnimationStart].
-         */
-        fun onLaunchAnimationAborted() {}
-    }
-
-    /** The state of an expandable view during an [ActivityLaunchAnimator] animation. */
-    open class State(
-        /** The position of the view in screen space coordinates. */
-        var top: Int,
-        var bottom: Int,
-        var left: Int,
-        var right: Int,
-
-        var topCornerRadius: Float = 0f,
-        var bottomCornerRadius: Float = 0f,
-
-        var contentAlpha: Float = 1f,
-        var backgroundAlpha: Float = 1f
-    ) {
-        private val startTop = top
-        private val startLeft = left
-        private val startRight = right
-
-        val width: Int
-            get() = right - left
-
-        val height: Int
-            get() = bottom - top
-
-        open val topChange: Int
-            get() = top - startTop
-
-        val leftChange: Int
-            get() = left - startLeft
-
-        val rightChange: Int
-            get() = right - startRight
-    }
-
-    @VisibleForTesting
-    class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
-        private val rootView = controller.getRootView()
-        @PublishedApi internal val context = rootView.context
-        private val transactionApplier = SyncRtSurfaceTransactionApplier(rootView)
-        private var animator: ValueAnimator? = null
-
-        private var windowCrop = Rect()
-        private var timedOut = false
-        private var cancelled = false
-
-        // A timeout to cancel the remote animation if it is not started within X milliseconds after
-        // the intent was started.
-        //
-        // Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise
-        // it will be automatically converted when posted and we wouldn't be able to remove it after
-        // posting it.
-        private var onTimeout = Runnable { onAnimationTimedOut() }
-
-        @PublishedApi
-        internal fun postTimeout() {
-            rootView.postDelayed(onTimeout, LAUNCH_TIMEOUT)
-        }
-
-        private fun removeTimeout() {
-            rootView.removeCallbacks(onTimeout)
-        }
-
-        override fun onAnimationStart(
-            @WindowManager.TransitionOldType transit: Int,
-            remoteAnimationTargets: Array<out RemoteAnimationTarget>,
-            remoteAnimationWallpaperTargets: Array<out RemoteAnimationTarget>,
-            remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
-            iRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback
-        ) {
-            removeTimeout()
-
-            // The animation was started too late and we already notified the controller that it
-            // timed out.
-            if (timedOut) {
-                invokeCallback(iRemoteAnimationFinishedCallback)
-                return
-            }
-
-            // This should not happen, but let's make sure we don't start the animation if it was
-            // cancelled before and we already notified the controller.
-            if (cancelled) {
-                return
-            }
-
-            context.mainExecutor.execute {
-                startAnimation(remoteAnimationTargets, iRemoteAnimationFinishedCallback)
-            }
-        }
-
-        private fun startAnimation(
-            remoteAnimationTargets: Array<out RemoteAnimationTarget>,
-            iCallback: IRemoteAnimationFinishedCallback
-        ) {
-            val window = remoteAnimationTargets.firstOrNull {
-                it.mode == RemoteAnimationTarget.MODE_OPENING
-            }
-
-            if (window == null) {
-                removeTimeout()
-                invokeCallback(iCallback)
-                controller.onLaunchAnimationAborted()
-                return
-            }
-
-            val navigationBar = remoteAnimationTargets.firstOrNull {
-                it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
-            }
-
-            // Start state.
-            val state = controller.createAnimatorState()
-
-            val startTop = state.top
-            val startBottom = state.bottom
-            val startLeft = state.left
-            val startRight = state.right
-
-            val startTopCornerRadius = state.topCornerRadius
-            val startBottomCornerRadius = state.bottomCornerRadius
-
-            // End state.
-            val windowBounds = window.screenSpaceBounds
-            val endTop = windowBounds.top
-            val endBottom = windowBounds.bottom
-            val endLeft = windowBounds.left
-            val endRight = windowBounds.right
-
-            // TODO(b/184121838): Ensure that we are launching on the same screen.
-            val rootViewLocation = rootView.locationOnScreen
-            val isExpandingFullyAbove = endTop <= rootViewLocation[1] &&
-                endBottom >= rootViewLocation[1] + rootView.height &&
-                endLeft <= rootViewLocation[0] &&
-                endRight >= rootViewLocation[0] + rootView.width
-
-            // TODO(b/184121838): We should somehow get the top and bottom radius of the window.
-            val endRadius = if (isExpandingFullyAbove) {
-                // Most of the time, expanding fully above the root view means expanding in full
-                // screen.
-                ScreenDecorationsUtils.getWindowCornerRadius(context.resources)
-            } else {
-                // This usually means we are in split screen mode, so 2 out of 4 corners will have
-                // a radius of 0.
-                0f
-            }
-
-            // Update state.
-            val animator = ValueAnimator.ofFloat(0f, 1f)
-            this.animator = animator
-            animator.duration = ANIMATION_DURATION
-            animator.interpolator = LINEAR_INTERPOLATOR
-
-            animator.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
-                    controller.onLaunchAnimationStart(isExpandingFullyAbove)
-                }
-
-                override fun onAnimationEnd(animation: Animator?) {
-                    invokeCallback(iCallback)
-                    controller.onLaunchAnimationEnd(isExpandingFullyAbove)
-                }
-            })
-
-            animator.addUpdateListener { animation ->
-                if (cancelled) {
-                    return@addUpdateListener
-                }
-
-                val linearProgress = animation.animatedFraction
-                val progress = ANIMATION_INTERPOLATOR.getInterpolation(linearProgress)
-
-                state.top = lerp(startTop, endTop, progress).roundToInt()
-                state.bottom = lerp(startBottom, endBottom, progress).roundToInt()
-                state.left = lerp(startLeft, endLeft, progress).roundToInt()
-                state.right = lerp(startRight, endRight, progress).roundToInt()
-
-                state.topCornerRadius = MathUtils.lerp(startTopCornerRadius, endRadius, progress)
-                state.bottomCornerRadius =
-                    MathUtils.lerp(startBottomCornerRadius, endRadius, progress)
-
-                val contentAlphaProgress = getProgress(linearProgress, 0,
-                        ANIMATION_DURATION_FADE_OUT_CONTENT)
-                state.contentAlpha =
-                        1 - ALPHA_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress)
-
-                val backgroundAlphaProgress = getProgress(linearProgress,
-                        ANIMATION_DURATION_FADE_OUT_CONTENT, ANIMATION_DURATION_FADE_IN_WINDOW)
-                state.backgroundAlpha =
-                        1 - ALPHA_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress)
-
-                applyStateToWindow(window, state)
-                navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
-                controller.onLaunchAnimationProgress(state, progress, linearProgress)
-            }
-
-            animator.start()
-        }
-
-        private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
-            val m = Matrix()
-            m.postTranslate(0f, (state.top - window.sourceContainerBounds.top).toFloat())
-            windowCrop.set(state.left, 0, state.right, state.height)
-
-            val cornerRadius = minOf(state.topCornerRadius, state.bottomCornerRadius)
-            val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
-                    .withAlpha(1f)
-                    .withMatrix(m)
-                    .withWindowCrop(windowCrop)
-                    .withLayer(window.prefixOrderIndex)
-                    .withCornerRadius(cornerRadius)
-                    .withVisibility(true)
-                    .build()
-
-            transactionApplier.scheduleApply(params)
-        }
-
-        private fun applyStateToNavigationBar(
-            navigationBar: RemoteAnimationTarget,
-            state: State,
-            linearProgress: Float
-        ) {
-            val fadeInProgress = getProgress(linearProgress, ANIMATION_DELAY_NAV_FADE_IN,
-                    ANIMATION_DURATION_NAV_FADE_OUT)
-
-            val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
-            if (fadeInProgress > 0) {
-                val m = Matrix()
-                m.postTranslate(0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat())
-                windowCrop.set(state.left, 0, state.right, state.height)
-                params
-                        .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
-                        .withMatrix(m)
-                        .withWindowCrop(windowCrop)
-                        .withVisibility(true)
-            } else {
-                val fadeOutProgress = getProgress(linearProgress, 0,
-                        ANIMATION_DURATION_NAV_FADE_OUT)
-                params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
-            }
-
-            transactionApplier.scheduleApply(params.build())
-        }
-
-        private fun onAnimationTimedOut() {
-            if (cancelled) {
-                return
-            }
-
-            timedOut = true
-            controller.onLaunchAnimationTimedOut()
-        }
-
-        override fun onAnimationCancelled() {
-            if (timedOut) {
-                return
-            }
-
-            cancelled = true
-            removeTimeout()
-            context.mainExecutor.execute {
-                animator?.cancel()
-                controller.onLaunchAnimationCancelled()
-            }
-        }
-
-        private fun invokeCallback(iCallback: IRemoteAnimationFinishedCallback) {
-            try {
-                iCallback.onAnimationFinished()
-            } catch (e: RemoteException) {
-                e.printStackTrace()
-            }
-        }
-
-        private fun lerp(start: Int, stop: Int, amount: Float): Float {
-            return MathUtils.lerp(start.toFloat(), stop.toFloat(), amount)
-        }
-    }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt
deleted file mode 100644
index a5494ad..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt
+++ /dev/null
@@ -1,329 +0,0 @@
-package com.android.systemui.plugins.animation
-
-import android.graphics.Canvas
-import android.graphics.ColorFilter
-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
-import android.graphics.drawable.LayerDrawable
-import android.view.GhostView
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-
-/**
- * A base implementation of [ActivityLaunchAnimator.Controller] which creates a [ghost][GhostView]
- * of [ghostedView] as well as an expandable background view, which are drawn and animated instead
- * of the ghosted view.
- *
- * Important: [ghostedView] must be attached to the window when calling this function and during the
- * animation.
- *
- * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
- * whenever possible instead.
- */
-open class GhostedViewLaunchAnimatorController(
-    /** The view that will be ghosted and from which the background will be extracted. */
-    private val ghostedView: View
-) : ActivityLaunchAnimator.Controller {
-    /** The root view to which we will add the ghost view and expanding background. */
-    private val rootView = ghostedView.rootView as ViewGroup
-    private val rootViewOverlay = rootView.overlay
-
-    /** The ghost view that is drawn and animated instead of the ghosted view. */
-    private var ghostView: View? = null
-
-    /**
-     * The expanding background view that will be added to [rootView] (below [ghostView]) and
-     * animate.
-     */
-    private var backgroundView: FrameLayout? = null
-
-    /**
-     * The drawable wrapping the [ghostedView] background and used as background for
-     * [backgroundView].
-     */
-    private var backgroundDrawable: WrappedDrawable? = null
-    private var startBackgroundAlpha: Int = 0xFF
-
-    /**
-     * Return the background of the [ghostedView]. This background will be used to draw the
-     * background of the background view that is expanding up to the final animation position. This
-     * is called at the start of the animation.
-     *
-     * Note that during the animation, the alpha value value of this background will be set to 0,
-     * then set back to its initial value at the end of the animation.
-     */
-    protected open fun getBackground(): Drawable? = ghostedView.background
-
-    /**
-     * Set the corner radius of [background]. The background is the one that was returned by
-     * [getBackground].
-     */
-    protected open fun setBackgroundCornerRadius(
-        background: Drawable,
-        topCornerRadius: Float,
-        bottomCornerRadius: Float
-    ) {
-        // By default, we rely on WrappedDrawable to set/restore the background radii before/after
-        // each draw.
-        backgroundDrawable?.setBackgroundRadius(topCornerRadius, bottomCornerRadius)
-    }
-
-    /** Return the current top corner radius of the background. */
-    protected open fun getCurrentTopCornerRadius(): Float {
-        val drawable = getBackground() ?: return 0f
-        val gradient = findGradientDrawable(drawable) ?: return 0f
-
-        // TODO(b/184121838): Support more than symmetric top & bottom radius.
-        return gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius
-    }
-
-    /** Return the current bottom corner radius of the background. */
-    protected open fun getCurrentBottomCornerRadius(): Float {
-        val drawable = getBackground() ?: return 0f
-        val gradient = findGradientDrawable(drawable) ?: return 0f
-
-        // TODO(b/184121838): Support more than symmetric top & bottom radius.
-        return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
-    }
-
-    override fun getRootView(): View {
-        return rootView
-    }
-
-    override fun createAnimatorState(): ActivityLaunchAnimator.State {
-        val location = ghostedView.locationOnScreen
-        return ActivityLaunchAnimator.State(
-            top = location[1],
-            bottom = location[1] + ghostedView.height,
-            left = location[0],
-            right = location[0] + ghostedView.width,
-            topCornerRadius = getCurrentTopCornerRadius(),
-            bottomCornerRadius = getCurrentBottomCornerRadius()
-        )
-    }
-
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-        backgroundView = FrameLayout(rootView.context).apply {
-            forceHasOverlappingRendering(false)
-        }
-        rootViewOverlay.add(backgroundView)
-
-        // We wrap the ghosted view background and use it to draw the expandable background. Its
-        // alpha will be set to 0 as soon as we start drawing the expanding background.
-        val drawable = getBackground()
-        startBackgroundAlpha = drawable?.alpha ?: 0xFF
-        backgroundDrawable = WrappedDrawable(drawable)
-        backgroundView?.background = backgroundDrawable
-
-        // 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, rootView).apply {
-            setLayerType(View.LAYER_TYPE_HARDWARE, null)
-        }
-    }
-
-    override fun onLaunchAnimationProgress(
-        state: ActivityLaunchAnimator.State,
-        progress: Float,
-        linearProgress: Float
-    ) {
-        val ghostView = this.ghostView!!
-        ghostView.translationX = (state.leftChange + state.rightChange) / 2.toFloat()
-        ghostView.translationY = state.topChange.toFloat()
-        ghostView.alpha = state.contentAlpha
-
-        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)
-        }
-    }
-
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-        backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
-
-        GhostView.removeGhost(ghostedView)
-        rootViewOverlay.remove(backgroundView)
-        ghostedView.invalidate()
-    }
-
-    companion object {
-        private const val CORNER_RADIUS_TOP_INDEX = 0
-        private const val CORNER_RADIUS_BOTTOM_INDEX = 4
-
-        /**
-         * Return the first [GradientDrawable] found in [drawable], or null if none is found. If
-         * [drawable] is a [LayerDrawable], this will return the first layer that is a
-         * [GradientDrawable].
-         */
-        private fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
-            if (drawable is GradientDrawable) {
-                return drawable
-            }
-
-            if (drawable is LayerDrawable) {
-                for (i in 0 until drawable.numberOfLayers) {
-                    val maybeGradient = drawable.getDrawable(i)
-                    if (maybeGradient is GradientDrawable) {
-                        return maybeGradient
-                    }
-                }
-            }
-
-            return null
-        }
-    }
-
-    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()
-
-        private var cornerRadii = FloatArray(8) { -1f }
-        private var previousCornerRadii = FloatArray(8)
-
-        override fun draw(canvas: Canvas) {
-            val wrapped = this.wrapped ?: return
-
-            wrapped.copyBounds(previousBounds)
-
-            wrapped.alpha = currentAlpha
-            wrapped.bounds = bounds
-            setXfermode(wrapped, SRC_MODE)
-            applyBackgroundRadii()
-
-            wrapped.draw(canvas)
-
-            // The background view (and therefore this drawable) is drawn before the ghost view, so
-            // the ghosted view background alpha should always be 0 when it is drawn above the
-            // background.
-            wrapped.alpha = 0
-            wrapped.bounds = previousBounds
-            setXfermode(wrapped, null)
-            restoreBackgroundRadii()
-        }
-
-        override fun setAlpha(alpha: Int) {
-            if (alpha != currentAlpha) {
-                currentAlpha = alpha
-                invalidateSelf()
-            }
-        }
-
-        override fun getAlpha() = currentAlpha
-
-        override fun getOpacity(): Int {
-            val wrapped = this.wrapped ?: return PixelFormat.TRANSPARENT
-
-            val previousAlpha = wrapped.alpha
-            wrapped.alpha = currentAlpha
-            val opacity = wrapped.opacity
-            wrapped.alpha = previousAlpha
-            return opacity
-        }
-
-        override fun setColorFilter(filter: ColorFilter?) {
-            wrapped?.colorFilter = filter
-        }
-
-        private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) {
-            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()
-        }
-
-        private fun updateRadii(
-            radii: FloatArray,
-            topCornerRadius: Float,
-            bottomCornerRadius: Float
-        ) {
-            radii[0] = topCornerRadius
-            radii[1] = topCornerRadius
-            radii[2] = topCornerRadius
-            radii[3] = topCornerRadius
-
-            radii[4] = bottomCornerRadius
-            radii[5] = bottomCornerRadius
-            radii[6] = bottomCornerRadius
-            radii[7] = bottomCornerRadius
-        }
-
-        private fun applyBackgroundRadii() {
-            if (cornerRadii[0] < 0 || wrapped == null) {
-                return
-            }
-
-            savePreviousBackgroundRadii(wrapped)
-            applyBackgroundRadii(wrapped, cornerRadii)
-        }
-
-        private fun savePreviousBackgroundRadii(background: Drawable) {
-            // TODO(b/184121838): This method assumes that all GradientDrawable in background will
-            // have the same radius. Should we save/restore the radii for each layer instead?
-            val gradient = findGradientDrawable(background) ?: return
-
-            // TODO(b/184121838): GradientDrawable#getCornerRadii clones its radii array. Should we
-            // try to avoid that?
-            val radii = gradient.cornerRadii
-            if (radii != null) {
-                radii.copyInto(previousCornerRadii)
-            } else {
-                // Copy the cornerRadius into previousCornerRadii.
-                val radius = gradient.cornerRadius
-                updateRadii(previousCornerRadii, radius, radius)
-            }
-        }
-
-        private fun applyBackgroundRadii(drawable: Drawable, radii: FloatArray) {
-            if (drawable is GradientDrawable) {
-                drawable.cornerRadii = radii
-                return
-            }
-
-            if (drawable !is LayerDrawable) {
-                return
-            }
-
-            for (i in 0 until drawable.numberOfLayers) {
-                (drawable.getDrawable(i) as? GradientDrawable)?.cornerRadii = radii
-            }
-        }
-
-        private fun restoreBackgroundRadii() {
-            if (cornerRadii[0] < 0 || wrapped == null) {
-                return
-            }
-
-            applyBackgroundRadii(wrapped, previousCornerRadii)
-        }
-    }
-}