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

Commit d6161f27 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

Hydrator is traceable.

Bug: 354269846
Test: smoke test with flexiglass - visited every scene before/after
unlock and after relock
Flag: com.android.systemui.scene_container

Change-Id: Iaa535e949662c91fcfe33e63647339e596084421
parent 19f87369
Loading
Loading
Loading
Loading
+51 −12
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.lifecycle
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.StateFactoryMarker
import com.android.app.tracing.coroutines.launch
import com.android.app.tracing.coroutines.traceCoroutine
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@@ -37,35 +39,55 @@ import kotlinx.coroutines.launch
 * }
 * ```
 */
class Hydrator : ExclusiveActivatable() {
class Hydrator(
    /**
     * A name for performance tracing purposes.
     *
     * Please use a short string literal that's easy to find in code search. Try to avoid
     * concatenation or templating.
     */
    private val traceName: String,
) : ExclusiveActivatable() {

    private val children = mutableListOf<Activatable>()
    private val children = mutableListOf<NamedActivatable>()

    /**
     * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
     * Returns a snapshot [State] that's kept up-to-date as long as its owner is active.
     *
     * @param traceName Used for coroutine performance tracing purposes. Please try to use a label
     *   that's unique enough and easy enough to find in code search; this should help correlate
     *   performance findings with actual code. One recommendation: prefer whole string literals
     *   instead of some complex concatenation or templating scheme.
     * @param source The upstream [StateFlow] to collect from; values emitted to it will be
     *   automatically set on the returned [State].
     */
    @StateFactoryMarker
    fun <T> hydratedStateOf(
        traceName: String,
        source: StateFlow<T>,
    ): State<T> {
        return hydratedStateOf(
            traceName = traceName,
            initialValue = source.value,
            source = source,
        )
    }

    /**
     * Returns a snapshot [State] that's kept up-to-date as long as the [SysUiViewModel] is active.
     * Returns a snapshot [State] that's kept up-to-date as long as its owner is active.
     *
     * @param traceName Used for coroutine performance tracing purposes. Please try to use a label
     *   that's unique enough and easy enough to find in code search; this should help correlate
     *   performance findings with actual code. One recommendation: prefer whole string literals
     *   instead of some complex concatenation or templating scheme. Use `null` to disable
     *   performance tracing for this state.
     * @param initialValue The first value to place on the [State]
     * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
     *   set on the returned [State].
     */
    @StateFactoryMarker
    fun <T> hydratedStateOf(
        traceName: String?,
        initialValue: T,
        source: Flow<T>,
    ): State<T> {
@@ -73,18 +95,35 @@ class Hydrator : ExclusiveActivatable() {

        val mutableState = mutableStateOf(initialValue)
        children.add(
            NamedActivatable(
                traceName = traceName,
                activatable =
                    object : ExclusiveActivatable() {
                        override suspend fun onActivated(): Nothing {
                            source.collect { mutableState.value = it }
                            awaitCancellation()
                        }
            }
                    },
            )
        )
        return mutableState
    }

    override suspend fun onActivated() = coroutineScope {
        children.forEach { child -> launch { child.activate() } }
        traceCoroutine(traceName) {
            children.forEach { child ->
                if (child.traceName != null) {
                    launch(spanName = child.traceName) { child.activatable.activate() }
                } else {
                    launch { child.activatable.activate() }
                }
            }
            awaitCancellation()
        }
    }

    private data class NamedActivatable(
        val traceName: String?,
        val activatable: Activatable,
    )
}
+2 −2
Original line number Diff line number Diff line
@@ -51,10 +51,10 @@ constructor(
    /** The scene that should be rendered. */
    val currentScene: StateFlow<SceneKey> = sceneInteractor.currentScene

    private val hydrator = Hydrator()
    private val hydrator = Hydrator("SceneContainerViewModel.hydrator")

    /** Whether the container is visible. */
    val isVisible: Boolean by hydrator.hydratedStateOf(sceneInteractor.isVisible)
    val isVisible: Boolean by hydrator.hydratedStateOf("isVisible", sceneInteractor.isVisible)

    override suspend fun onActivated(): Nothing {
        try {
+11 −3
Original line number Diff line number Diff line
@@ -33,10 +33,18 @@ class FakeSysUiViewModel(
    var activationCount = 0
    var cancellationCount = 0

    private val hydrator = Hydrator()
    private val hydrator = Hydrator("test")
    val stateBackedByFlow: Boolean by
        hydrator.hydratedStateOf(initialValue = true, source = upstreamFlow)
    val stateBackedByStateFlow: Boolean by hydrator.hydratedStateOf(source = upstreamStateFlow)
        hydrator.hydratedStateOf(
            traceName = "test",
            initialValue = true,
            source = upstreamFlow,
        )
    val stateBackedByStateFlow: Boolean by
        hydrator.hydratedStateOf(
            traceName = "test",
            source = upstreamStateFlow,
        )

    override suspend fun onActivated(): Nothing {
        activationCount++