• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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) { row -> initialValue.logFull(row) }
74         initialValue
75     }
76     return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T ->
77         tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
78         newVal
79     }
80 }
81 
82 // Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
83 // above [logDiffsForTable] method.
84 
85 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull86 fun Flow<Boolean>.logDiffsForTable(
87     tableLogBuffer: TableLogBuffer,
88     columnPrefix: String,
89     columnName: String,
90     initialValue: Boolean,
91 ): Flow<Boolean> {
92     val initialValueFun = {
93         tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
94         initialValue
95     }
96     return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean ->
97         if (prevVal != newVal) {
98             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
99         }
100         newVal
101     }
102 }
103 
104 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull105 fun Flow<Int>.logDiffsForTable(
106     tableLogBuffer: TableLogBuffer,
107     columnPrefix: String,
108     columnName: String,
109     initialValue: Int,
110 ): Flow<Int> {
111     val initialValueFun = {
112         tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
113         initialValue
114     }
115     return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int ->
116         if (prevVal != newVal) {
117             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
118         }
119         newVal
120     }
121 }
122 
123 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull124 fun Flow<Int?>.logDiffsForTable(
125     tableLogBuffer: TableLogBuffer,
126     columnPrefix: String,
127     columnName: String,
128     initialValue: Int?,
129 ): Flow<Int?> {
130     val initialValueFun = {
131         tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
132         initialValue
133     }
134     return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
135         if (prevVal != newVal) {
136             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
137         }
138         newVal
139     }
140 }
141 
142 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull143 fun Flow<String?>.logDiffsForTable(
144     tableLogBuffer: TableLogBuffer,
145     columnPrefix: String,
146     columnName: String,
147     initialValue: String?,
148 ): Flow<String?> {
149     val initialValueFun = {
150         tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
151         initialValue
152     }
153     return this.pairwiseBy(initialValueFun) { prevVal, newVal: String? ->
154         if (prevVal != newVal) {
155             tableLogBuffer.logChange(columnPrefix, columnName, newVal)
156         }
157         newVal
158     }
159 }
160 
161 /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
logDiffsForTablenull162 fun <T> Flow<List<T>>.logDiffsForTable(
163     tableLogBuffer: TableLogBuffer,
164     columnPrefix: String,
165     columnName: String,
166     initialValue: List<T>,
167 ): Flow<List<T>> {
168     val initialValueFun = {
169         tableLogBuffer.logChange(columnPrefix, columnName, initialValue.toString())
170         initialValue
171     }
172     return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
173         if (prevVal != newVal) {
174             // TODO(b/267761156): Can we log list changes without using toString?
175             tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
176         }
177         newVal
178     }
179 }
180