• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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