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

Commit 494beb04 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Automerger Merge Worker
Browse files

Merge "Revisit how launch animations handle View visibility" into tm-qpr-dev am: 9758d71b

parents 48710b2c 9758d71b
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -791,13 +791,13 @@ private class AnimatedDialog(
        // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
        // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
        // one-off synchronization to make sure that this is done in sync between the two different
        // one-off synchronization to make sure that this is done in sync between the two different
        // windows.
        // windows.
        controller.startDrawingInOverlayOf(decorView)
        synchronizeNextDraw(
        synchronizeNextDraw(
            then = {
            then = {
                isSourceDrawnInDialog = true
                isSourceDrawnInDialog = true
                maybeStartLaunchAnimation()
                maybeStartLaunchAnimation()
            }
            }
        )
        )
        controller.startDrawingInOverlayOf(decorView)
    }
    }


    /**
    /**
+17 −10
Original line number Original line Diff line number Diff line
@@ -195,14 +195,16 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
        backgroundDrawable = WrappedDrawable(background)
        backgroundDrawable = WrappedDrawable(background)
        backgroundView?.background = backgroundDrawable
        backgroundView?.background = backgroundDrawable


        // Delay the calls to `ghostedView.setVisibility()` during the animation. This must be
        // called before `GhostView.addGhost()` is called because the latter will change the
        // *transition* visibility, which won't be blocked and will affect the normal View
        // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration.
        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)

        // Create a ghost of the view that will be moving and fading out. This allows to fade out
        // 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.
        // the content before fading out the background.
        ghostView = GhostView.addGhost(ghostedView, launchContainer)
        ghostView = GhostView.addGhost(ghostedView, launchContainer)


        // The ghost was just created, so ghostedView is currently invisible. We need to make sure
        // that it stays invisible as long as we are animating.
        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)

        val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
        val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
        matrix.getValues(initialGhostViewMatrixValues)
        matrix.getValues(initialGhostViewMatrixValues)


@@ -297,15 +299,20 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
        backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
        backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha


        GhostView.removeGhost(ghostedView)
        GhostView.removeGhost(ghostedView)
        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
        launchContainerOverlay.remove(backgroundView)
        launchContainerOverlay.remove(backgroundView)


        // Make sure that the view is considered VISIBLE by accessibility by first making it
        if (ghostedView is LaunchableView) {
        // INVISIBLE then VISIBLE (see b/204944038#comment17 for more info).
            // Restore the ghosted view visibility.
            ghostedView.setShouldBlockVisibilityChanges(false)
        } else {
            // Make the ghosted view visible. We ensure that the view is considered VISIBLE by
            // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
            // for more info).
            ghostedView.visibility = View.INVISIBLE
            ghostedView.visibility = View.INVISIBLE
            ghostedView.visibility = View.VISIBLE
            ghostedView.visibility = View.VISIBLE
            ghostedView.invalidate()
            ghostedView.invalidate()
        }
        }
    }


    companion object {
    companion object {
        private const val CORNER_RADIUS_TOP_INDEX = 0
        private const val CORNER_RADIUS_TOP_INDEX = 0
+21 −25
Original line number Original line Diff line number Diff line
@@ -21,15 +21,19 @@ import android.view.View
/** A view that can expand/launch into an app or a dialog. */
/** A view that can expand/launch into an app or a dialog. */
interface LaunchableView {
interface LaunchableView {
    /**
    /**
     * Set whether this view should block/postpone all visibility changes. This ensures that this
     * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures
     * view:
     * that this view:
     * - remains invisible during the launch animation given that it is ghosted and already drawn
     * - remains invisible during the launch animation given that it is ghosted and already drawn
     * somewhere else.
     * somewhere else.
     * - remains invisible as long as a dialog expanded from it is shown.
     * - remains invisible as long as a dialog expanded from it is shown.
     * - restores its expected visibility once the dialog expanded from it is dismissed.
     * - restores its expected visibility once the dialog expanded from it is dismissed.
     *
     *
     * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
     * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should
     * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
     * be restored to its expected value, i.e. it should have the visibility of the last call to
     * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any,
     * or the original view visibility otherwise.
     *
     * Note that calls to [View.setTransitionVisibility] shouldn't be blocked.
     *
     *
     * @param block whether we should block/postpone all calls to `setVisibility` and
     * @param block whether we should block/postpone all calls to `setVisibility` and
     * `setTransitionVisibility`.
     * `setTransitionVisibility`.
@@ -46,29 +50,33 @@ class LaunchableViewDelegate(
     * super.setVisibility(visibility).
     * super.setVisibility(visibility).
     */
     */
    private val superSetVisibility: (Int) -> Unit,
    private val superSetVisibility: (Int) -> Unit,

) : LaunchableView {
    /**
     * The lambda that should set the actual transition visibility of [view], usually by calling
     * super.setTransitionVisibility(visibility).
     */
    private val superSetTransitionVisibility: (Int) -> Unit,
) {
    private var blockVisibilityChanges = false
    private var blockVisibilityChanges = false
    private var lastVisibility = view.visibility
    private var lastVisibility = view.visibility


    /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
    /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
    fun setShouldBlockVisibilityChanges(block: Boolean) {
    override fun setShouldBlockVisibilityChanges(block: Boolean) {
        if (block == blockVisibilityChanges) {
        if (block == blockVisibilityChanges) {
            return
            return
        }
        }


        blockVisibilityChanges = block
        blockVisibilityChanges = block
        if (block) {
        if (block) {
            // Save the current visibility for later.
            lastVisibility = view.visibility
            lastVisibility = view.visibility
        } else {
        } else {
            // Restore the visibility. To avoid accessibility issues, we change the visibility twice
            // which makes sure that we trigger a visibility flag change (see b/204944038#comment17
            // for more info).
            if (lastVisibility == View.VISIBLE) {
                superSetVisibility(View.INVISIBLE)
                superSetVisibility(View.VISIBLE)
            } else {
                superSetVisibility(View.VISIBLE)
                superSetVisibility(lastVisibility)
                superSetVisibility(lastVisibility)
            }
            }
        }
        }
    }


    /** Call this when [View.setVisibility] is called. */
    /** Call this when [View.setVisibility] is called. */
    fun setVisibility(visibility: Int) {
    fun setVisibility(visibility: Int) {
@@ -79,16 +87,4 @@ class LaunchableViewDelegate(


        superSetVisibility(visibility)
        superSetVisibility(visibility)
    }
    }

    /** Call this when [View.setTransitionVisibility] is called. */
    fun setTransitionVisibility(visibility: Int) {
        if (blockVisibilityChanges) {
            // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
            // the transition visibility separately from the normal visibility.
            lastVisibility = visibility
            return
        }

        superSetTransitionVisibility(visibility)
    }
}
}
+33 −21
Original line number Original line Diff line number Diff line
@@ -34,24 +34,30 @@ internal constructor(
    override val sourceIdentity: Any = source
    override val sourceIdentity: Any = source


    override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
    override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
        // Delay the calls to `source.setVisibility()` during the animation. This must be called
        // before `GhostView.addGhost()` is called because the latter will change the *transition*
        // visibility, which won't be blocked and will affect the normal View visibility that is
        // saved by `setShouldBlockVisibilityChanges()` for a later restoration.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)

        // Create a temporary ghost of the source (which will make it invisible) and add it
        // Create a temporary ghost of the source (which will make it invisible) and add it
        // to the host dialog.
        // to the host dialog.
        GhostView.addGhost(source, viewGroup)
        GhostView.addGhost(source, viewGroup)

        // The ghost of the source was just created, so the source is currently invisible.
        // We need to make sure that it stays invisible as long as the dialog is shown or
        // animating.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
    }
    }


    override fun stopDrawingInOverlay() {
    override fun stopDrawingInOverlay() {
        // Note: here we should remove the ghost from the overlay, but in practice this is
        // Note: here we should remove the ghost from the overlay, but in practice this is
        // already done by the launch controllers created below.
        // already done by the launch controller created below.


        // Make sure we allow the source to change its visibility again.
        if (source is LaunchableView) {
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
            // Make sure we allow the source to change its visibility again and restore its previous
            // value.
            source.setShouldBlockVisibilityChanges(false)
        } else {
            // We made the source invisible earlier, so let's make it visible again.
            source.visibility = View.VISIBLE
            source.visibility = View.VISIBLE
        }
        }
    }


    override fun createLaunchController(): LaunchAnimator.Controller {
    override fun createLaunchController(): LaunchAnimator.Controller {
        val delegate = GhostedViewLaunchAnimatorController(source)
        val delegate = GhostedViewLaunchAnimatorController(source)
@@ -67,13 +73,17 @@ internal constructor(
            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)


                // We hide the source when the dialog is showing. We will make this view
                // At this point the view visibility is restored by the delegate, so we delay the
                // visible again when dismissing the dialog. This does nothing if the source
                // visibility changes again and make it invisible while the dialog is shown.
                // implements [LaunchableView], as it's already INVISIBLE in that case.
                if (source is LaunchableView) {
                    source.setShouldBlockVisibilityChanges(true)
                    source.setTransitionVisibility(View.INVISIBLE)
                } else {
                    source.visibility = View.INVISIBLE
                    source.visibility = View.INVISIBLE
                }
                }
            }
            }
        }
        }
    }


    override fun createExitController(): LaunchAnimator.Controller {
    override fun createExitController(): LaunchAnimator.Controller {
        return GhostedViewLaunchAnimatorController(source)
        return GhostedViewLaunchAnimatorController(source)
@@ -90,15 +100,17 @@ internal constructor(
    }
    }


    override fun onExitAnimationCancelled() {
    override fun onExitAnimationCancelled() {
        if (source is LaunchableView) {
            // Make sure we allow the source to change its visibility again.
            // Make sure we allow the source to change its visibility again.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
            source.setShouldBlockVisibilityChanges(false)

        } else {
            // If the view is invisible it's probably because of us, so we make it visible
            // If the view is invisible it's probably because of us, so we make it visible
            // again.
            // again.
            if (source.visibility == View.INVISIBLE) {
            if (source.visibility == View.INVISIBLE) {
                source.visibility = View.VISIBLE
                source.visibility = View.VISIBLE
            }
            }
        }
        }
    }


    override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
    override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
        val type = cuj?.cujType ?: return null
        val type = cuj?.cujType ?: return null
+0 −5
Original line number Original line Diff line number Diff line
@@ -28,7 +28,6 @@ class LaunchableImageView : ImageView, LaunchableView {
        LaunchableViewDelegate(
        LaunchableViewDelegate(
            this,
            this,
            superSetVisibility = { super.setVisibility(it) },
            superSetVisibility = { super.setVisibility(it) },
            superSetTransitionVisibility = { super.setTransitionVisibility(it) },
        )
        )


    constructor(context: Context?) : super(context)
    constructor(context: Context?) : super(context)
@@ -53,8 +52,4 @@ class LaunchableImageView : ImageView, LaunchableView {
    override fun setVisibility(visibility: Int) {
    override fun setVisibility(visibility: Int) {
        delegate.setVisibility(visibility)
        delegate.setVisibility(visibility)
    }
    }

    override fun setTransitionVisibility(visibility: Int) {
        delegate.setTransitionVisibility(visibility)
    }
}
}
Loading