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

Commit 1699d32a authored by Hawkwood Glazier's avatar Hawkwood Glazier Committed by Automerger Merge Worker
Browse files

Merge "YouTube and YTMusic trigger a race which causes the AnimatorSet to...

Merge "YouTube and YTMusic trigger a race which causes the AnimatorSet to hang" into tm-dev am: 7a7ef611

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18038469



Change-Id: I454ff0355839888e685e6efd8ff0a5235eca7f33
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 642a6436 7a7ef611
Loading
Loading
Loading
Loading
+0 −25
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -553,30 +552,6 @@ public class MediaControlPanel {

                // refreshState is required here to resize the text views (and prevent ellipsis)
                mMediaViewController.refreshState();

                // Use OnPreDrawListeners to enforce zero alpha on these views for a frame.
                // TransitionLayout insists on resetting the alpha of these views to 1 when onLayout
                // is called which causes the animation to look bad. These suppress that behavior.
                titleText.getViewTreeObserver().addOnPreDrawListener(
                        new ViewTreeObserver.OnPreDrawListener() {
                            @Override
                            public boolean onPreDraw() {
                                titleText.setAlpha(0);
                                titleText.getViewTreeObserver().removeOnPreDrawListener(this);
                                return true;
                            }
                        });

                artistText.getViewTreeObserver().addOnPreDrawListener(
                        new ViewTreeObserver.OnPreDrawListener() {
                            @Override
                            public boolean onPreDraw() {
                                artistText.setAlpha(0);
                                artistText.getViewTreeObserver().removeOnPreDrawListener(this);
                                return true;
                            }
                        });

                return Unit.INSTANCE;
            },
            () -> {
+8 −18
Original line number Diff line number Diff line
@@ -18,8 +18,6 @@ package com.android.systemui.media

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import com.android.internal.annotations.VisibleForTesting

/**
 * MetadataAnimationHandler controls the current state of the MediaControlPanel's transition motion.
@@ -33,37 +31,37 @@ internal open class MetadataAnimationHandler(
    private val enterAnimator: Animator
) : AnimatorListenerAdapter() {

    private val animator: AnimatorSet
    private var postExitUpdate: (() -> Unit)? = null
    private var postEnterUpdate: (() -> Unit)? = null
    private var targetData: Any? = null

    val isRunning: Boolean
        get() = animator.isRunning
        get() = enterAnimator.isRunning || exitAnimator.isRunning

    fun setNext(targetData: Any, postExit: () -> Unit, postEnter: () -> Unit): Boolean {
        if (targetData != this.targetData) {
            this.targetData = targetData
            postExitUpdate = postExit
            postEnterUpdate = postEnter
            if (!animator.isRunning) {
                animator.start()
            if (!isRunning) {
                exitAnimator.start()
            }
            return true
        }
        return false
    }

    override fun onAnimationEnd(animator: Animator) {
        if (animator === exitAnimator) {
    override fun onAnimationEnd(anim: Animator) {
        if (anim === exitAnimator) {
            postExitUpdate?.let { it() }
            postExitUpdate = null
            enterAnimator.start()
        }

        if (animator === enterAnimator) {
        if (anim === enterAnimator) {
            // Another new update appeared while entering
            if (postExitUpdate != null) {
                this.animator.start()
                exitAnimator.start()
            } else {
                postEnterUpdate?.let { it() }
                postEnterUpdate = null
@@ -74,13 +72,5 @@ internal open class MetadataAnimationHandler(
    init {
        exitAnimator.addListener(this)
        enterAnimator.addListener(this)
        animator = buildAnimatorSet(exitAnimator, enterAnimator)
    }

    @VisibleForTesting
    protected open fun buildAnimatorSet(exit: Animator, enter: Animator): AnimatorSet {
        val result = AnimatorSet()
        result.playSequentially(exitAnimator, enterAnimator)
        return result
    }
}
 No newline at end of file
+2 −2
Original line number Diff line number Diff line
@@ -947,7 +947,7 @@ public class MediaControlPanelTest : SysuiTestCase() {

        // Rebinding should not trigger animation
        player.bindPlayer(mediaData, PACKAGE)
        verify(mockAnimator, times(1)).start()
        verify(mockAnimator, times(2)).start()
    }

    @Test
@@ -969,7 +969,7 @@ public class MediaControlPanelTest : SysuiTestCase() {

        // Bind trigges new animation
        player.bindPlayer(data1, PACKAGE)
        verify(mockAnimator, times(2)).start()
        verify(mockAnimator, times(3)).start()
        whenever(mockAnimator.isRunning()).thenReturn(true)

        // Rebind before animation end binds corrct data
+42 −18
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.media

import org.mockito.Mockito.`when` as whenever
import android.animation.Animator
import android.animation.AnimatorSet
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -44,7 +43,6 @@ class MetadataAnimationHandlerTest : SysuiTestCase() {
    private interface Callback : () -> Unit
    private lateinit var handler: MetadataAnimationHandler

    @Mock private lateinit var animatorSet: AnimatorSet
    @Mock private lateinit var enterAnimator: Animator
    @Mock private lateinit var exitAnimator: Animator
    @Mock private lateinit var postExitCB: Callback
@@ -54,11 +52,7 @@ class MetadataAnimationHandlerTest : SysuiTestCase() {

    @Before
    fun setUp() {
        handler = object : MetadataAnimationHandler(exitAnimator, enterAnimator) {
            override fun buildAnimatorSet(exit: Animator, enter: Animator): AnimatorSet {
                return animatorSet
            }
        }
        handler = MetadataAnimationHandler(exitAnimator, enterAnimator)
    }

    @After
@@ -69,22 +63,31 @@ class MetadataAnimationHandlerTest : SysuiTestCase() {
        val cb = { fail("Unexpected callback") }
        handler.setNext("data-1", cb, cb)

        verify(animatorSet).start()
        verify(exitAnimator).start()
    }

    @Test
    fun executeAnimationEnd_runsCallacks() {
        // We expect this first call to only start the exit animator
        handler.setNext("data-1", postExitCB, postEnterCB)
        verify(animatorSet, times(1)).start()
        verify(exitAnimator, times(1)).start()
        verify(enterAnimator, never()).start()
        verify(postExitCB, never()).invoke()
        verify(postEnterCB, never()).invoke()

        // After the exit animator completes,
        // the exit cb should run, and enter animation should start
        handler.onAnimationEnd(exitAnimator)
        verify(animatorSet, times(1)).start()
        verify(exitAnimator, times(1)).start()
        verify(enterAnimator, times(1)).start()
        verify(postExitCB, times(1)).invoke()
        verify(postEnterCB, never()).invoke()

        // After the exit animator completes,
        // the enter cb should run without other state changes
        handler.onAnimationEnd(enterAnimator)
        verify(animatorSet, times(1)).start()
        verify(exitAnimator, times(1)).start()
        verify(enterAnimator, times(1)).start()
        verify(postExitCB, times(1)).invoke()
        verify(postEnterCB, times(1)).invoke()
    }
@@ -120,38 +123,58 @@ class MetadataAnimationHandlerTest : SysuiTestCase() {
        val postExitCB2 = mock(Callback::class.java)
        val postEnterCB2 = mock(Callback::class.java)

        // We expect this first call to only start the exit animator
        handler.setNext("data-1", postExitCB, postEnterCB)
        verify(animatorSet, times(1)).start()
        verify(exitAnimator, times(1)).start()
        verify(enterAnimator, never()).start()
        verify(postExitCB, never()).invoke()
        verify(postExitCB2, never()).invoke()
        verify(postEnterCB, never()).invoke()
        verify(postEnterCB2, never()).invoke()

        whenever(animatorSet.isRunning()).thenReturn(true)
        // After the exit animator completes,
        // the exit cb should run, and enter animation should start
        whenever(exitAnimator.isRunning()).thenReturn(true)
        whenever(enterAnimator.isRunning()).thenReturn(false)
        handler.onAnimationEnd(exitAnimator)
        verify(animatorSet, times(1)).start()
        verify(exitAnimator, times(1)).start()
        verify(enterAnimator, times(1)).start()
        verify(postExitCB, times(1)).invoke()
        verify(postExitCB2, never()).invoke()
        verify(postEnterCB, never()).invoke()
        verify(postEnterCB2, never()).invoke()

        // Setting new data before the enter animator completes should not trigger
        // the exit animator an additional time (since it's already running)
        whenever(exitAnimator.isRunning()).thenReturn(false)
        whenever(enterAnimator.isRunning()).thenReturn(true)
        handler.setNext("data-2", postExitCB2, postEnterCB2)
        verify(exitAnimator, times(1)).start()

        // Finishing the enterAnimator should cause the exitAnimator to fire again
        // since the data change and additional time. No enterCB should be executed.
        handler.onAnimationEnd(enterAnimator)
        verify(animatorSet, times(2)).start()
        verify(exitAnimator, times(2)).start()
        verify(enterAnimator, times(1)).start()
        verify(postExitCB, times(1)).invoke()
        verify(postExitCB2, never()).invoke()
        verify(postEnterCB, never()).invoke()
        verify(postEnterCB2, never()).invoke()

        // Continuing the sequence, this triggers the enter animator an additional time
        handler.onAnimationEnd(exitAnimator)
        verify(animatorSet, times(2)).start()
        verify(exitAnimator, times(2)).start()
        verify(enterAnimator, times(2)).start()
        verify(postExitCB, times(1)).invoke()
        verify(postExitCB2, times(1)).invoke()
        verify(postEnterCB, never()).invoke()
        verify(postEnterCB2, never()).invoke()

        // And finally the enter animator completes,
        // triggering the correct postEnterCallback to fire
        handler.onAnimationEnd(enterAnimator)
        verify(animatorSet, times(2)).start()
        verify(exitAnimator, times(2)).start()
        verify(enterAnimator, times(2)).start()
        verify(postExitCB, times(1)).invoke()
        verify(postExitCB2, times(1)).invoke()
        verify(postEnterCB, never()).invoke()
@@ -172,6 +195,7 @@ class MetadataAnimationHandlerTest : SysuiTestCase() {
    fun enterAnimatorEndsWithoutCallback_noAnimatiorStart() {
        handler.onAnimationEnd(enterAnimator)

        verify(animatorSet, never()).start()
        verify(exitAnimator, never()).start()
        verify(enterAnimator, never()).start()
    }
}