1 /*
<lambda>null2  * Copyright (C) 2022 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.log.table
18 
19 import com.android.systemui.util.kotlin.pairwiseBy
20 import kotlinx.coroutines.flow.Flow
21 
22 /**
23  * An interface that enables logging the difference between values in table format.
24  *
25  * Many objects that we want to log are data-y objects with a collection of fields. When logging
26  * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
27  * highlight changes in individual fields.
28  *
29  * See [TableLogBuffer].
30  */
31 interface Diffable<T> {
32     /**
33      * Finds the differences between [prevVal] and this object and logs those diffs to [row].
34      *
35      * Each implementer should determine which individual fields have changed between [prevVal] and
36      * this object, and only log the fields that have actually changed. This helps save buffer
37      * space.
38      *
39      * For example, if:
40      * - prevVal = Object(val1=100, val2=200, val3=300)
41      * - this = Object(val1=100, val2=200, val3=333)
42      *
43      * Then only the val3 change should be logged.
44      */
45     fun logDiffs(prevVal: T, row: TableRowLogger)
46 
47     /**
48      * Logs all the relevant fields of this object to [row].
49      *
50      * As opposed to [logDiffs], this method should log *all* fields.
51      *
52      * Implementation is optional. This method will only be used with [logDiffsForTable] in order to
53      * fully log the initial value of the flow.
54      */
55     fun logFull(row: TableRowLogger) {}
56 }
57 
58 /**
59  * Each time the flow is updated with a new value, logs the differences between the previous value
60  * and the new value to the given [tableLogBuffer].
61  *
62  * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
63  *
64  * @param columnPrefix a prefix that will be applied to every column name that gets logged.
65  */
logDiffsForTablenull66 fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
67     tableLogBuffer: TableLogBuffer,
68     columnPrefix: String,
69     initialValue: T,
70 ): Flow<T> {
71     // Fully log the initial value to the table.
72     val getInitialValue = {
73         tableLogBuffer.logChange(columnPrefix, isInitial = true) { row ->
74             initialValue.logFull(row)
75         }
76         initialValue
77     }
78     return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T ->
79         tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
80         newVal
81     }
82 }
83 
84 // Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
85 // above [logDiffsForTable] method.
86 
87 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull88 fun Flow<Boolean>.logDiffsForTable(
89     tableLogBuffer: TableLogBuffer,
90     columnPrefix: String,
91     columnName: String,
92     initialValue: Boolean,
93 ): Flow<Boolean> {
94     val initialValueFun = {
95         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
96         initialValue
97     }
98     return this.pairwiseBy(initialValueFun) { prevVal: Boolean, newVal: Boolean ->
99         if (prevVal != newVal) {
100             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
101         }
102         newVal
103     }
104 }
105 
106 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull107 fun Flow<Int>.logDiffsForTable(
108     tableLogBuffer: TableLogBuffer,
109     columnPrefix: String,
110     columnName: String,
111     initialValue: Int,
112 ): Flow<Int> {
113     val initialValueFun = {
114         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
115         initialValue
116     }
117     return this.pairwiseBy(initialValueFun) { prevVal: Int, newVal: Int ->
118         if (prevVal != newVal) {
119             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
120         }
121         newVal
122     }
123 }
124 
125 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull126 fun Flow<Int?>.logDiffsForTable(
127     tableLogBuffer: TableLogBuffer,
128     columnPrefix: String,
129     columnName: String,
130     initialValue: Int?,
131 ): Flow<Int?> {
132     val initialValueFun = {
133         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
134         initialValue
135     }
136     return this.pairwiseBy(initialValueFun) { prevVal: Int?, newVal: Int? ->
137         if (prevVal != newVal) {
138             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
139         }
140         newVal
141     }
142 }
143 
144 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull145 fun Flow<String?>.logDiffsForTable(
146     tableLogBuffer: TableLogBuffer,
147     columnPrefix: String,
148     columnName: String,
149     initialValue: String?,
150 ): Flow<String?> {
151     val initialValueFun = {
152         tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
153         initialValue
154     }
155     return this.pairwiseBy(initialValueFun) { prevVal: String?, newVal: String? ->
156         if (prevVal != newVal) {
157             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
158         }
159         newVal
160     }
161 }
162 
163 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull164 fun <T> Flow<List<T>>.logDiffsForTable(
165     tableLogBuffer: TableLogBuffer,
166     columnPrefix: String,
167     columnName: String,
168     initialValue: List<T>,
169 ): Flow<List<T>> {
170     val initialValueFun = {
171         tableLogBuffer.logChange(
172             columnPrefix,
173             columnName,
174             initialValue.toString(),
175             isInitial = true,
176         )
177         initialValue
178     }
179     return this.pairwiseBy(initialValueFun) { prevVal: List<T>, newVal: List<T> ->
180         if (prevVal != newVal) {
181             // TODO(b/267761156): Can we log list changes without using toString?
182             tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
183         }
184         newVal
185     }
186 }
187