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

Commit e625c978 authored by Jernej Virag's avatar Jernej Virag
Browse files

Warn if UDPFS Lottie animation runs when screen is off

We report one Log.wtf if we detect animation frames being rendered as
the screen is off. This happens only on debug and eng builds.

Bug: 420685896
Flag: com.android.systemui.screen_off_animation_guard_enabled
Test: built for tangorpro and verified

Change-Id: I9914d6970e82a23145eaed7326d434d4bc29b2fb
parent 6b20127d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -2019,3 +2019,13 @@ flag {
      purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "screen_off_animation_guard_enabled"
    namespace: "systemui"
    description: "Reports Log.wtf statements if animations are running while screen is off."
    bug: "420685896"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -300,4 +300,8 @@

    <!-- id for the composable battery meter (see UnifiedBattery.kt) -->
    <item type="id" name="battery_meter_composable_view" />

    <!-- Used for animation guard tag to throttle number of reported logs. -->
    <item type="id" name="screen_off_animation_guard_set" />
    <item type="id" name="screen_off_animation_guard_reported_wtf" />
</resources>
+85 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.util.animation

import android.animation.ValueAnimator
import android.content.res.Resources
import android.os.Build
import android.util.Log
import android.view.Display
import android.view.View
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieDrawable
import com.android.systemui.Flags.screenOffAnimationGuardEnabled
import com.android.systemui.res.R
import java.lang.ref.WeakReference

private const val LOG_TAG = "AnimationGuard"

/**
 * This observes a given animation view and reports a WTF if the animations are running while the
 * screen is off.
 */
fun LottieAnimationView.enableScreenOffAnimationGuard() {
    if (!screenOffAnimationGuardEnabled()) {
        return
    }

    if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
        return
    }

    val lottieDrawable = drawable as? LottieDrawable ?: return
    if (getTag(R.id.screen_off_animation_guard_set) == System.identityHashCode(lottieDrawable)) {
        return
    }

    val animationView = WeakReference(this)
    val screenOffListenerGuard: ValueAnimator.AnimatorUpdateListener =
        ValueAnimator.AnimatorUpdateListener {
            animationView.get()?.let { view ->
                if (view.getTag(R.id.screen_off_animation_guard_reported_wtf) == true) {
                    return@AnimatorUpdateListener
                }

                // Retrieve ID of the view rendering
                val viewIdName =
                    try {
                        if (view.id != View.NO_ID) {
                            view.resources.getResourceEntryName(view.id)
                        } else {
                            "no-id"
                        }
                    } catch (e: Resources.NotFoundException) {
                        view.id.toString()
                    }

                val isScreenOff = view.display?.state == Display.STATE_OFF
                if (isScreenOff) {
                    // These logs create Binder calls, so throttle them. One is enough.
                    Log.wtf(LOG_TAG, "Lottie view $viewIdName is running while screen is off")
                    view.setTag(R.id.screen_off_animation_guard_reported_wtf, true)
                } else if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
                    Log.d(LOG_TAG, "Lottie view $viewIdName is running while screen is on")
                }
            }
        }

    setTag(R.id.screen_off_animation_guard_reported_wtf, false)
    lottieDrawable.addAnimatorUpdateListener(screenOffListenerGuard)
    setTag(R.id.screen_off_animation_guard_set, System.identityHashCode(lottieDrawable))
}
+9 −1
Original line number Diff line number Diff line
@@ -19,17 +19,25 @@ import android.content.Context
import android.util.AttributeSet
import com.airbnb.lottie.LottieAnimationView
import com.android.app.tracing.traceSection
import com.android.systemui.util.animation.enableScreenOffAnimationGuard

/** LottieAnimationView that traces each call to invalidate. */
open class LottieViewWrapper : LottieAnimationView {
    constructor(context: Context?) : super(context)

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    constructor(
        context: Context?,
        attrs: AttributeSet?,
        defStyleAttr: Int
        defStyleAttr: Int,
    ) : super(context, attrs, defStyleAttr)

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        enableScreenOffAnimationGuard()
    }

    override fun invalidate() {
        traceSection<Any?>("${this::class} invalidate") {
            super.invalidate()