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