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.util.asIndenting 23 import com.android.systemui.util.printCollection 24 import java.io.PrintWriter 25 import java.util.concurrent.ConcurrentHashMap 26 import java.util.concurrent.atomic.AtomicBoolean 27 import kotlinx.coroutines.flow.Flow 28 import kotlinx.coroutines.flow.SharedFlow 29 import kotlinx.coroutines.flow.StateFlow 30 import kotlinx.coroutines.flow.flow 31 32 /** 33 * An interface which gives the implementing type flow extension functions which will register a 34 * given flow as a field in the Dumpable. 35 */ 36 interface FlowDumper : Dumpable { 37 /** 38 * Include the last emitted value of this Flow whenever it is being collected. Remove its value 39 * when collection ends. 40 * 41 * @param dumpName the name to use for this field in the dump output 42 */ 43 fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> 44 45 /** 46 * Include the [SharedFlow.replayCache] for this Flow in the dump. 47 * 48 * @param dumpName the name to use for this field in the dump output 49 */ 50 fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F 51 52 /** 53 * Include the [StateFlow.value] for this Flow in the dump. 54 * 55 * @param dumpName the name to use for this field in the dump output 56 */ 57 fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F 58 59 /** The default [Dumpable.dump] implementation which just calls [dumpFlows] */ 60 override fun dump(pw: PrintWriter, args: Array<out String>) = dumpFlows(pw.asIndenting()) 61 62 /** Dump all the values from any registered / active Flows. */ 63 fun dumpFlows(pw: IndentingPrintWriter) 64 } 65 66 /** 67 * An implementation of [FlowDumper]. This be extended directly, or can be used to implement 68 * [FlowDumper] by delegation. 69 * 70 * @param dumpManager if provided, this will be used by the [FlowDumperImpl] to register and 71 * unregister itself when there is something to dump. 72 * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this 73 * class's name will be used. If you're implementing by delegation, you probably want to provide 74 * this tag to get a meaningful dumpable name. 75 */ 76 open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = null) : FlowDumper { 77 private val stateFlowMap = ConcurrentHashMap<String, StateFlow<*>>() 78 private val sharedFlowMap = ConcurrentHashMap<String, SharedFlow<*>>() 79 private val flowCollectionMap = ConcurrentHashMap<Pair<String, String>, Any>() dumpFlowsnull80 override fun dumpFlows(pw: IndentingPrintWriter) { 81 pw.printCollection("StateFlow (value)", stateFlowMap.toSortedMap().entries) { (key, flow) -> 82 append(key).append('=').println(flow.value) 83 } 84 pw.printCollection("SharedFlow (replayCache)", sharedFlowMap.toSortedMap().entries) { 85 (key, flow) -> 86 append(key).append('=').println(flow.replayCache) 87 } 88 val comparator = compareBy<Pair<String, String>> { it.first }.thenBy { it.second } 89 pw.printCollection("Flow (latest)", flowCollectionMap.toSortedMap(comparator).entries) { 90 (pair, value) -> 91 append(pair.first).append('=').println(value) 92 } 93 } 94 95 private val Any.idString: String 96 get() = Integer.toHexString(System.identityHashCode(this)) 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 updateRegistration(required = true) 104 emit(it) 105 } 106 } finally { 107 flowCollectionMap.remove(mapKey) 108 updateRegistration(required = false) 109 } 110 } 111 dumpValuenull112 override fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F { 113 stateFlowMap[dumpName] = this 114 return this 115 } 116 dumpReplayCachenull117 override fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F { 118 sharedFlowMap[dumpName] = this 119 return this 120 } 121 122 private val dumpManagerName = tag ?: "[$idString] ${javaClass.simpleName}" 123 private var registered = AtomicBoolean(false) updateRegistrationnull124 private fun updateRegistration(required: Boolean) { 125 if (dumpManager == null) return 126 if (required && registered.get()) return 127 synchronized(registered) { 128 val shouldRegister = 129 stateFlowMap.isNotEmpty() || 130 sharedFlowMap.isNotEmpty() || 131 flowCollectionMap.isNotEmpty() 132 val wasRegistered = registered.getAndSet(shouldRegister) 133 if (wasRegistered != shouldRegister) { 134 if (shouldRegister) { 135 dumpManager.registerCriticalDumpable(dumpManagerName, this) 136 } else { 137 dumpManager.unregisterDumpable(dumpManagerName) 138 } 139 } 140 } 141 } 142 } 143