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