1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.lifecycle 18 19 import androidx.compose.runtime.State 20 import androidx.compose.runtime.mutableStateOf 21 import androidx.compose.runtime.snapshots.StateFactoryMarker 22 import com.android.app.tracing.coroutines.launchTraced as launch 23 import com.android.app.tracing.coroutines.traceCoroutine 24 import com.android.systemui.log.table.TableLogBuffer 25 import kotlinx.coroutines.awaitCancellation 26 import kotlinx.coroutines.coroutineScope 27 import kotlinx.coroutines.flow.Flow 28 import kotlinx.coroutines.flow.StateFlow 29 30 /** 31 * Keeps snapshot/Compose [State]s up-to-date. 32 * 33 * ```kotlin 34 * val hydrator = Hydrator() 35 * val state: Int by hydrator.hydratedStateOf(upstreamFlow) 36 * 37 * override suspend fun activate(): Nothing { 38 * hydrator.activate() 39 * } 40 * ``` 41 */ 42 class Hydrator( 43 /** 44 * A name for performance tracing purposes. 45 * 46 * Please use a short string literal that's easy to find in code search. Try to avoid 47 * concatenation or templating. 48 */ 49 private val traceName: String, 50 /** 51 * An optional [TableLogBuffer] to log emissions to the states. [traceName] will be used as the 52 * prefix for the columns logged by this [Hydrator], allowing to aggregate multiple hydrators in 53 * the same table. 54 */ 55 private val tableLogBuffer: TableLogBuffer? = null, 56 ) : ExclusiveActivatable() { 57 58 private val children = mutableListOf<NamedActivatable>() 59 60 /** 61 * Returns a snapshot [State] that's kept up-to-date as long as its owner is active. 62 * 63 * @param traceName Used for coroutine performance tracing purposes. Please try to use a label 64 * that's unique enough and easy enough to find in code search; this should help correlate 65 * performance findings with actual code. One recommendation: prefer whole string literals 66 * instead of some complex concatenation or templating scheme. 67 * @param source The upstream [StateFlow] to collect from; values emitted to it will be 68 * automatically set on the returned [State]. 69 */ 70 @StateFactoryMarker 71 fun <T> hydratedStateOf(traceName: String, source: StateFlow<T>): State<T> { 72 return hydratedStateOf(traceName = traceName, initialValue = source.value, source = source) 73 } 74 75 /** 76 * Returns a snapshot [State] that's kept up-to-date as long as its owner is active. 77 * 78 * @param traceName Used for coroutine performance tracing purposes. Please try to use a label 79 * that's unique enough and easy enough to find in code search; this should help correlate 80 * performance findings with actual code. One recommendation: prefer whole string literals 81 * instead of some complex concatenation or templating scheme. Use `null` to disable 82 * performance tracing for this state. 83 * 84 * If a [TableLogBuffer] was provided, every emission to the flow will be logged using the 85 * [traceName] as the column name. For this to work correctly, all the states in the same 86 * hydrator should have different [traceName]. Use `null` to disable logging for this state. 87 * 88 * @param initialValue The first value to place on the [State] 89 * @param source The upstream [Flow] to collect from; values emitted to it will be automatically 90 * set on the returned [State]. 91 */ 92 @StateFactoryMarker 93 fun <T> hydratedStateOf(traceName: String?, initialValue: T, source: Flow<T>): State<T> { 94 check(!isActive) { "Cannot call hydratedStateOf after Hydrator is already active." } 95 96 val mutableState = mutableStateOf(initialValue) 97 traceName?.let { name -> 98 tableLogBuffer?.logChange( 99 prefix = this.traceName, 100 columnName = name, 101 value = initialValue?.toString(), 102 isInitial = true, 103 ) 104 } 105 children.add( 106 NamedActivatable( 107 traceName = traceName, 108 activatable = 109 object : ExclusiveActivatable() { 110 override suspend fun onActivated(): Nothing { 111 source.collect { 112 traceName?.let { name -> 113 tableLogBuffer?.logChange( 114 prefix = this@Hydrator.traceName, 115 columnName = name, 116 value = it?.toString(), 117 ) 118 } 119 mutableState.value = it 120 } 121 awaitCancellation() 122 } 123 }, 124 ) 125 ) 126 return mutableState 127 } 128 129 override suspend fun onActivated() = coroutineScope { 130 traceCoroutine(traceName) { 131 children.forEach { child -> 132 if (child.traceName != null) { 133 launch(spanName = child.traceName) { child.activatable.activate() } 134 } else { 135 launch { child.activatable.activate() } 136 } 137 } 138 awaitCancellation() 139 } 140 } 141 142 private data class NamedActivatable(val traceName: String?, val activatable: Activatable) 143 } 144