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

Commit 64d182a6 authored by Jernej Virag's avatar Jernej Virag
Browse files

Add support for AndroidX to ScreenOffAnimationGuard

Also adds a bit of leeway to not report on frames close to screen off.

Bug: 420685896
Test: ran ScreenOffAnimationGuardTest
Flag: com.android.systemui.screen_off_animation_guard_enabled

Change-Id: I3f027efcee4adc3c86727ffcfbc9c8236657c9b0
parent 6fa77f07
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. */