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.util.kotlin 18 19 import android.util.IndentingPrintWriter 20 import com.android.systemui.Dumpable 21 import com.android.systemui.dump.DumpManager 22 import com.android.systemui.lifecycle.ExclusiveActivatable 23 import com.android.systemui.util.asIndenting 24 import com.android.systemui.util.printCollection 25 import java.io.PrintWriter 26 import java.util.concurrent.ConcurrentHashMap 27 import java.util.concurrent.atomic.AtomicBoolean 28 import kotlinx.coroutines.awaitCancellation 29 import kotlinx.coroutines.flow.Flow 30 import kotlinx.coroutines.flow.SharedFlow 31 import kotlinx.coroutines.flow.StateFlow 32 import kotlinx.coroutines.flow.flow 33 34 /** 35 * An interface which gives the implementing type flow extension functions which will register a 36 * given flow as a field in the Dumpable. 37 */ 38 interface FlowDumper : Dumpable { 39 /** 40 * Include the last emitted value of this Flow whenever it is being collected. Remove its value 41 * when collection ends. 42 * 43 * @param dumpName the name to use for this field in the dump output 44 */ 45 fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> 46 47 /** 48 * Include the [SharedFlow.replayCache] for this Flow in the dump. 49 * 50 * @param dumpName the name to use for this field in the dump output 51 */ 52 fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F 53 54 /** 55 * Include the [StateFlow.value] for this Flow in the dump. 56 * 57 * @param dumpName the name to use for this field in the dump output 58 */ 59 fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F 60 61 /** The default [Dumpable.dump] implementation which just calls [dumpFlows] */ 62 override fun dump(pw: PrintWriter, args: Array<out String>) = dumpFlows(pw.asIndenting()) 63 64 /** Dump all the values from any registered / active Flows. */ 65 fun dumpFlows(pw: IndentingPrintWriter) 66 } 67 68 /** 69 * The minimal implementation of FlowDumper. The owner must either register this with the 70 * DumpManager, or else call [dumpFlows] from its own [Dumpable.dump] method. 71 */ 72 open class SimpleFlowDumper : FlowDumper { 73 74 private val stateFlowMap = ConcurrentHashMap<String, StateFlow<*>>() 75 private val sharedFlowMap = ConcurrentHashMap<String, SharedFlow<*>>() 76 private val flowCollectionMap = ConcurrentHashMap<Pair<String, String>, Any>() 77 isNotEmptynull78 protected fun isNotEmpty(): Boolean = 79 stateFlowMap.isNotEmpty() || sharedFlowMap.isNotEmpty() || flowCollectionMap.isNotEmpty() 80 81 protected open fun onMapKeysChanged(added: Boolean) {} 82 dumpFlowsnull83 override fun dumpFlows(pw: IndentingPrintWriter) { 84 pw.printCollection("StateFlow (value)", stateFlowMap.toSortedMap().entries) { (key, flow) -> 85 append(key).append('=').println(flow.value) 86 } 87 pw.printCollection("SharedFlow (replayCache)", sharedFlowMap.toSortedMap().entries) { 88 (key, flow) -> 89 append(key).append('=').println(flow.replayCache) 90 } 91 val comparator = compareBy<Pair<String, String>> { it.first }.thenBy { it.second } 92 pw.printCollection("Flow (latest)", flowCollectionMap.toSortedMap(comparator).entries) { 93 (pair, value) -> 94 append(pair.first).append('=').println(value) 95 } 96 } 97 <lambda>null98 override fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> = flow { 99 val mapKey = dumpName to idString 100 try { 101 collect { 102 flowCollectionMap[mapKey] = it ?: "null" 103 onMapKeysChanged(added = true) 104 emit(it) 105 } 106 } finally { 107 flowCollectionMap.remove(mapKey) 108 onMapKeysChanged(added = false) 109 } 110 } 111 dumpValuenull112 override fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F { 113 stateFlowMap[dumpName] = this 114 onMapKeysChanged(added = true) 115 return this 116 } 117 dumpReplayCachenull118 override fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F { 119 sharedFlowMap[dumpName] = this 120 onMapKeysChanged(added = true) 121 return this 122 } 123 124 protected val Any.idString: String 125 get() = Integer.toHexString(System.identityHashCode(this)) 126 } 127 128 /** 129 * An implementation of [FlowDumper] that registers itself whenever there is something to dump. This 130 * class is meant to be extended. 131 * 132 * @param dumpManager this will be used by the [FlowDumperImpl] to register and unregister itself 133 * when there is something to dump. 134 * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this 135 * class's name will be used. 136 */ 137 abstract class FlowDumperImpl( 138 private val dumpManager: DumpManager, 139 private val tag: String? = null, 140 ) : SimpleFlowDumper() { 141 onMapKeysChangednull142 override fun onMapKeysChanged(added: Boolean) { 143 updateRegistration(required = added) 144 } 145 146 private val dumpManagerName = "[$idString] ${tag ?: javaClass.simpleName}" 147 148 private var registered = AtomicBoolean(false) 149 updateRegistrationnull150 private fun updateRegistration(required: Boolean) { 151 if (required && registered.get()) return 152 synchronized(registered) { 153 val shouldRegister = isNotEmpty() 154 val wasRegistered = registered.getAndSet(shouldRegister) 155 if (wasRegistered != shouldRegister) { 156 if (shouldRegister) { 157 dumpManager.registerCriticalDumpable(dumpManagerName, this) 158 } else { 159 dumpManager.unregisterDumpable(dumpManagerName) 160 } 161 } 162 } 163 } 164 } 165 166 /** 167 * A [FlowDumper] that also has an [activateFlowDumper] suspend function that allows the dumper to 168 * be registered with the [DumpManager] only when activated, just like 169 * [Activatable.activate()][com.android.systemui.lifecycle.Activatable.activate]. 170 */ 171 interface ActivatableFlowDumper : FlowDumper { activateFlowDumpernull172 suspend fun activateFlowDumper(): Nothing 173 } 174 175 /** 176 * Implementation of [ActivatableFlowDumper] that only registers when activated. 177 * 178 * This is generally used to implement [ActivatableFlowDumper] by delegation, especially for 179 * [SysUiViewModel] implementations. 180 * 181 * @param dumpManager used to automatically register and unregister this instance when activated and 182 * there is something to dump. 183 * @param tag the name with which this is dumper registered. 184 */ 185 class ActivatableFlowDumperImpl( 186 private val dumpManager: DumpManager, 187 tag: String, 188 ) : SimpleFlowDumper(), ActivatableFlowDumper { 189 190 private val registration = 191 object : ExclusiveActivatable() { 192 override suspend fun onActivated(): Nothing { 193 try { 194 dumpManager.registerCriticalDumpable( 195 dumpManagerName, 196 this@ActivatableFlowDumperImpl 197 ) 198 awaitCancellation() 199 } finally { 200 dumpManager.unregisterDumpable(dumpManagerName) 201 } 202 } 203 } 204 205 private val dumpManagerName = "[$idString] $tag" 206 207 override suspend fun activateFlowDumper(): Nothing { 208 registration.activate() 209 } 210 } 211