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

Commit ddf53fe7 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add support for AndroidX to ScreenOffAnimationGuard" into main

parents f9e1a776 64d182a6
Loading
Loading
Loading
Loading
+50 −4
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.util.animation

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorTestRule
import android.animation.ValueAnimator
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -30,15 +31,18 @@ import com.android.systemui.log.assertLogsWtfs
import com.android.systemui.runOnMainThreadAndWaitForIdleSync
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ScreenOffAnimationGuardKtTest : SysuiTestCase() {
class ScreenOffAnimationGuardTest : SysuiTestCase() {

    private val kosmos = Kosmos()

    @get:Rule public val animatorTestRuleX = androidx.core.animation.AnimatorTestRule()

    @Test
    @EnableFlags(Flags.FLAG_SCREEN_OFF_ANIMATION_GUARD_ENABLED)
    fun enableScreenOffAnimationGuard_screenOn_allGood() {
@@ -46,7 +50,7 @@ class ScreenOffAnimationGuardKtTest : SysuiTestCase() {

        val latch = CountDownLatch(1)
        valueAnimator.enableScreenOffAnimationGuard({ false })
        valueAnimator.duration = 10
        valueAnimator.duration = 100
        valueAnimator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
@@ -67,7 +71,7 @@ class ScreenOffAnimationGuardKtTest : SysuiTestCase() {

        val latch = CountDownLatch(1)
        valueAnimator.enableScreenOffAnimationGuard({ true })
        valueAnimator.duration = 10
        valueAnimator.duration = 100
        valueAnimator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
@@ -82,6 +86,48 @@ class ScreenOffAnimationGuardKtTest : SysuiTestCase() {
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_SCREEN_OFF_ANIMATION_GUARD_ENABLED)
    fun enableScreenOffAnimationGuardX_screenOn_allGood() {
        val valueAnimator = androidx.core.animation.ValueAnimator.ofFloat(0.0f, 1.0f)
        val latch = CountDownLatch(1)
        valueAnimator.enableScreenOffAnimationGuard({ false })
        valueAnimator.duration = 100
        valueAnimator.addListener(
            object : androidx.core.animation.AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: androidx.core.animation.Animator) {
                    latch.countDown()
                }
            }
        )
        runOnMainThreadAndWaitForIdleSync { valueAnimator.start() }

        latch.await(1, TimeUnit.SECONDS)
        // No Log.WTF
    }

    @Test
    @EnableFlags(Flags.FLAG_SCREEN_OFF_ANIMATION_GUARD_ENABLED)
    fun enableScreenOffAnimationGuardX_screenOff_reportsWtf() {
        val valueAnimator = androidx.core.animation.ValueAnimator.ofFloat(0.0f, 1.0f)

        val latch = CountDownLatch(1)
        valueAnimator.enableScreenOffAnimationGuard({ true })
        valueAnimator.duration = 100
        valueAnimator.addListener(
            object : androidx.core.animation.AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: androidx.core.animation.Animator) {
                    latch.countDown()
                }
            }
        )

        assertLogsWtfs {
            runOnMainThreadAndWaitForIdleSync { valueAnimator.start() }
            latch.await(1, TimeUnit.SECONDS)
        }
    }

    @Test
    @DisableFlags(Flags.FLAG_SCREEN_OFF_ANIMATION_GUARD_ENABLED)
    fun enableScreenOffAnimationGuard_screenOff_flagDisabled_doesNotReportWtf() {
@@ -89,7 +135,7 @@ class ScreenOffAnimationGuardKtTest : SysuiTestCase() {

        val latch = CountDownLatch(1)
        valueAnimator.enableScreenOffAnimationGuard({ true })
        valueAnimator.duration = 10
        valueAnimator.duration = 100
        valueAnimator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
+96 −10
Original line number Diff line number Diff line
@@ -26,8 +26,10 @@ import android.view.Display
import android.view.View
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieDrawable
import com.android.app.tracing.traceSection
import com.android.systemui.Flags.screenOffAnimationGuardEnabled
import com.android.systemui.res.R
import com.android.systemui.util.weakReference
import java.lang.ref.WeakReference

private const val LOG_TAG = "AnimationGuard"
@@ -95,7 +97,8 @@ fun ValueAnimator.enableScreenOffAnimationGuard(context: Context) {
        return
    }

    enableScreenOffAnimationGuard({ context.display.state == Display.STATE_OFF })
    val weakContext by weakReference(context)
    enableScreenOffAnimationGuard({ weakContext?.display?.state == Display.STATE_OFF })
}

/** Attaches an animation guard listener to the given ValueAnimator. */
@@ -109,38 +112,121 @@ fun ValueAnimator.enableScreenOffAnimationGuard(isDisplayOffPredicate: () -> Boo
    this.addUpdateListener(listener)
}

/**
 * Attaches a listener which will report a [Log.wtf] error if the animator is attempting to render
 * frames while the screen is off.
 */
fun androidx.core.animation.ValueAnimator.enableScreenOffAnimationGuard(context: Context) {
    if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
        return
    }

    val weakContext by weakReference(context)
    enableScreenOffAnimationGuard({ weakContext?.display?.state == Display.STATE_OFF })
}

/** Attaches an animation guard listener to the given ValueAnimator. */
fun androidx.core.animation.ValueAnimator.enableScreenOffAnimationGuard(
    isDisplayOffPredicate: () -> Boolean
) {
    if (!screenOffAnimationGuardEnabled()) {
        return
    }

    val listener = ScreenOffAnimationGuardListener(isDisplayOffPredicate)
    this.addListener(listener)
    this.addUpdateListener(listener)
}

/**
 * Remembers the stack trace of started animation and then reports an error if it runs when screen
 * is off.
 *
 * The implementation is a bit wonky because we account for both platform and AndroidX versions of
 * the listener.
 */
private class ScreenOffAnimationGuardListener(private val isDisplayOffPredicate: () -> Boolean) :
    Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
    Animator.AnimatorListener,
    ValueAnimator.AnimatorUpdateListener,
    androidx.core.animation.Animator.AnimatorListener,
    androidx.core.animation.Animator.AnimatorUpdateListener {

    val FRAMES_WHEN_SCREEN_OFF_LIMIT = 2

    // Holds the exception stack trace for the report.
    var animationStartedStackTrace: Exception? = null
    var framesRenderedWhileScreenIsOff = 0
    var animationDuringScreenOffReported = false

    /* Start animation, platform version. */
    override fun onAnimationStart(animation: Animator) {
        // This captures the stack trace of the starter of this animation.
        animationStartedStackTrace =
            AnimationDuringScreenOffException("Animation running during screen off.")
        animationDuringScreenOffReported = false
        captureAnimationStackTrace()
    }

    /* Start animation, AndroidX version. */
    override fun onAnimationStart(animation: androidx.core.animation.Animator) {
        captureAnimationStackTrace()
    }

    /* End animation, platform version. */
    override fun onAnimationEnd(animation: Animator) {
        animationStartedStackTrace = null
    }

    /* End animation, AndroidX version. */
    override fun onAnimationEnd(animation: androidx.core.animation.Animator) {
        animationStartedStackTrace = null
    }

    /* Animation step, platform version. */
    override fun onAnimationUpdate(animation: ValueAnimator) {
        if (!animationDuringScreenOffReported && isDisplayOffPredicate()) {
            Log.wtf(LOG_TAG, "View animator running during screen off.", animationStartedStackTrace)
            animationDuringScreenOffReported = true
        checkAndReportAnimationDuringScreenOff()
    }

    /* Animation step, AndroidX version. */
    override fun onAnimationUpdate(animation: androidx.core.animation.Animator) {
        checkAndReportAnimationDuringScreenOff()
    }

    override fun onAnimationCancel(animation: Animator) {}

    override fun onAnimationCancel(animation: androidx.core.animation.Animator) {}

    override fun onAnimationRepeat(animation: Animator) {}

    override fun onAnimationRepeat(animation: androidx.core.animation.Animator) {}

    /**
     * Stores the stack trace of "startAnimation" caller so we can determine which animator is
     * actually running.
     */
    fun captureAnimationStackTrace() =
        traceSection("captureAnimationStackTrace") {
            // This captures the stack trace of the starter of this animation.
            animationStartedStackTrace =
                AnimationDuringScreenOffException("Animation running during screen off.")
        }

    /** Reports WTF if we detect the animation running during screen off with saved stack trace. */
    fun checkAndReportAnimationDuringScreenOff() {
        if (!animationDuringScreenOffReported && isDisplayOffPredicate()) {
            // We want to give a bit of a leeway to make sure we don't report on animators
            // that race against screen off - that is, animators that _just_ complete as
            // the screen turned off.
            framesRenderedWhileScreenIsOff++
            if (framesRenderedWhileScreenIsOff < FRAMES_WHEN_SCREEN_OFF_LIMIT) {
                return
            }
            traceSection("reportAnimationDuringScreenOff") {
                Log.wtf(
                    LOG_TAG,
                    "View animator running during screen off.",
                    animationStartedStackTrace,
                )
                animationDuringScreenOffReported = true
            }
        }
    }
}

/** Used to record the stack trace of animation starter. */