diff options
15 files changed, 1164 insertions, 11 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt index f9e341c8629a..d6e29e0f2067 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -16,9 +16,9 @@ package com.android.systemui.log -import android.app.ActivityManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogcatEchoTracker @@ -29,15 +29,6 @@ class LogBufferFactory @Inject constructor( private val dumpManager: DumpManager, private val logcatEchoTracker: LogcatEchoTracker ) { - /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */ - private fun adjustMaxSize(requestedMaxSize: Int): Int { - return if (ActivityManager.isLowRamDeviceStatic()) { - minOf(requestedMaxSize, 20) /* low ram max log size*/ - } else { - requestedMaxSize - } - } - @JvmOverloads fun create( name: String, diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt new file mode 100644 index 000000000000..619eac1dbdc3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import android.app.ActivityManager + +class LogBufferHelper { + companion object { + /** If necessary, returns a limited maximum size for low ram (Go) devices */ + fun adjustMaxSize(requestedMaxSize: Int): Int { + return if (ActivityManager.isLowRamDeviceStatic()) { + minOf(requestedMaxSize, 20) /* low ram max log size*/ + } else { + requestedMaxSize + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt new file mode 100644 index 000000000000..c27bfa338df3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +import com.android.systemui.util.kotlin.pairwiseBy +import kotlinx.coroutines.flow.Flow + +/** + * An interface that enables logging the difference between values in table format. + * + * Many objects that we want to log are data-y objects with a collection of fields. When logging + * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily + * highlight changes in individual fields. + * + * See [TableLogBuffer]. + */ +interface Diffable<T> { + /** + * Finds the differences between [prevVal] and [this] and logs those diffs to [row]. + * + * Each implementer should determine which individual fields have changed between [prevVal] and + * [this], and only log the fields that have actually changed. This helps save buffer space. + * + * For example, if: + * - prevVal = Object(val1=100, val2=200, val3=300) + * - this = Object(val1=100, val2=200, val3=333) + * + * Then only the val3 change should be logged. + */ + fun logDiffs(prevVal: T, row: TableRowLogger) +} + +/** + * Each time the flow is updated with a new value, logs the differences between the previous value + * and the new value to the given [tableLogBuffer]. + * + * The new value's [Diffable.logDiffs] method will be used to log the differences to the table. + * + * @param columnPrefix a prefix that will be applied to every column name that gets logged. + */ +fun <T : Diffable<T>> Flow<T>.logDiffsForTable( + tableLogBuffer: TableLogBuffer, + columnPrefix: String, + initialValue: T, +): Flow<T> { + return this.pairwiseBy(initialValue) { prevVal, newVal -> + tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal) + newVal + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt new file mode 100644 index 000000000000..68c297f76ab7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +/** + * A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable. + * + * Each message represents a change to exactly 1 type, specified by [DataType]. + */ +data class TableChange( + var timestamp: Long = 0, + var columnPrefix: String = "", + var columnName: String = "", + var type: DataType = DataType.EMPTY, + var bool: Boolean = false, + var int: Int = 0, + var str: String? = null, +) { + /** Resets to default values so that the object can be recycled. */ + fun reset(timestamp: Long, columnPrefix: String, columnName: String) { + this.timestamp = timestamp + this.columnPrefix = columnPrefix + this.columnName = columnName + this.type = DataType.EMPTY + this.bool = false + this.int = 0 + this.str = null + } + + /** Sets this to store a string change. */ + fun set(value: String?) { + type = DataType.STRING + str = value + } + + /** Sets this to store a boolean change. */ + fun set(value: Boolean) { + type = DataType.BOOLEAN + bool = value + } + + /** Sets this to store an int change. */ + fun set(value: Int) { + type = DataType.INT + int = value + } + + /** Returns true if this object has a change. */ + fun hasData(): Boolean { + return columnName.isNotBlank() && type != DataType.EMPTY + } + + fun getName(): String { + return if (columnPrefix.isNotBlank()) { + "$columnPrefix.$columnName" + } else { + columnName + } + } + + fun getVal(): String { + return when (type) { + DataType.EMPTY -> null + DataType.STRING -> str + DataType.INT -> int + DataType.BOOLEAN -> bool + }.toString() + } + + enum class DataType { + STRING, + BOOLEAN, + INT, + EMPTY, + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt new file mode 100644 index 000000000000..429637a0ee4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable +import com.android.systemui.dump.DumpManager +import com.android.systemui.dump.DumpsysTableLogger +import com.android.systemui.plugins.util.RingBuffer +import com.android.systemui.util.time.SystemClock +import java.io.PrintWriter +import java.text.SimpleDateFormat +import java.util.Locale +import kotlinx.coroutines.flow.Flow + +/** + * A logger that logs changes in table format. + * + * Some parts of System UI maintain a lot of pieces of state at once. + * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events: + * + * - 10-10 10:10:10.456: state2 updated to newVal2 + * - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1) + * - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2) + * - 10-10 10:11:05.123: state1 updated to newVal1 + * - 10-10 10:11:06.000: stateN updated to StateN(val1=false, val2=3) + * + * However, it can sometimes be more useful to view the state changes in table format: + * + * - timestamp--------- | state1- | state2- | ... | stateN.val1 | stateN.val2 + * - ------------------------------------------------------------------------- + * - 10-10 10:10:10.123 | val1--- | val2--- | ... | false------ | 0----------- + * - 10-10 10:10:10.456 | val1--- | newVal2 | ... | false------ | 0----------- + * - 10-10 10:11:00.000 | val1--- | newVal2 | ... | true------- | 1----------- + * - 10-10 10:11:02.123 | val1--- | newVal2 | ... | true------- | 2----------- + * - 10-10 10:11:05.123 | newVal1 | newVal2 | ... | true------- | 2----------- + * - 10-10 10:11:06.000 | newVal1 | newVal2 | ... | false------ | 3----------- + * + * This class enables easy logging of the state changes in both change event format and table + * format. + * + * This class also enables easy logging of states that are a collection of fields. For example, + * stateN in the above example consists of two fields -- val1 and val2. It's useful to put each + * field into its own column so that ABT (Android Bug Tool) can easily highlight changes to + * individual fields. + * + * How it works: + * + * 1) Create an instance of this buffer via [TableLogBufferFactory]. + * + * 2) For any states being logged, implement [Diffable]. Implementing [Diffable] allows the state to + * only log the fields that have *changed* since the previous update, instead of always logging all + * fields. + * + * 3) Each time a change in a state happens, call [logDiffs]. If your state is emitted using a + * [Flow], you should use the [logDiffsForTable] extension function to automatically log diffs any + * time your flow emits a new value. + * + * When a dump occurs, there will be two dumps: + * + * 1) The change events under the dumpable name "$name-changes". + * + * 2) This class will coalesce all the diffs into a table format and log them under the dumpable + * name "$name-table". + * + * @param maxSize the maximum size of the buffer. Must be > 0. + */ +class TableLogBuffer( + maxSize: Int, + private val name: String, + private val systemClock: SystemClock, +) { + init { + if (maxSize <= 0) { + throw IllegalArgumentException("maxSize must be > 0") + } + } + + private val buffer = RingBuffer(maxSize) { TableChange() } + + // A [TableRowLogger] object, re-used each time [logDiffs] is called. + // (Re-used to avoid object allocation.) + private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this) + + /** + * Log the differences between [prevVal] and [newVal]. + * + * The [newVal] object's method [Diffable.logDiffs] will be used to fetch the diffs. + * + * @param columnPrefix a prefix that will be applied to every column name that gets logged. This + * ensures that all the columns related to the same state object will be grouped together in the + * table. + */ + @Synchronized + fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) { + val row = tempRow + row.timestamp = systemClock.currentTimeMillis() + row.columnPrefix = columnPrefix + newVal.logDiffs(prevVal, row) + } + + // Keep these individual [logChange] methods private (don't let clients give us their own + // timestamps.) + + private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) { + val change = obtain(timestamp, prefix, columnName) + change.set(value) + } + + private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) { + val change = obtain(timestamp, prefix, columnName) + change.set(value) + } + + private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) { + val change = obtain(timestamp, prefix, columnName) + change.set(value) + } + + // TODO(b/259454430): Add additional change types here. + + @Synchronized + private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange { + val tableChange = buffer.advance() + tableChange.reset(timestamp, prefix, columnName) + return tableChange + } + + /** + * Registers this buffer as dumpables in [dumpManager]. Must be called for the table to be + * dumped. + * + * This will be automatically called in [TableLogBufferFactory.create]. + */ + fun registerDumpables(dumpManager: DumpManager) { + dumpManager.registerNormalDumpable("$name-changes", changeDumpable) + dumpManager.registerNormalDumpable("$name-table", tableDumpable) + } + + private val changeDumpable = Dumpable { pw, _ -> dumpChanges(pw) } + private val tableDumpable = Dumpable { pw, _ -> dumpTable(pw) } + + /** Dumps the list of [TableChange] objects. */ + @Synchronized + @VisibleForTesting + fun dumpChanges(pw: PrintWriter) { + for (i in 0 until buffer.size) { + buffer[i].dump(pw) + } + } + + /** Dumps an individual [TableChange]. */ + private fun TableChange.dump(pw: PrintWriter) { + if (!this.hasData()) { + return + } + val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp) + pw.print(formattedTimestamp) + pw.print(" ") + pw.print(this.getName()) + pw.print("=") + pw.print(this.getVal()) + pw.println() + } + + /** + * Coalesces all the [TableChange] objects into a table of values of time and dumps the table. + */ + // TODO(b/259454430): Since this is an expensive process, it could cause the bug report dump to + // fail and/or not dump anything else. We should move this processing to ABT (Android Bug + // Tool), where we have unlimited time to process. + @Synchronized + @VisibleForTesting + fun dumpTable(pw: PrintWriter) { + val messages = buffer.iterator().asSequence().toList() + + if (messages.isEmpty()) { + return + } + + // Step 1: Create list of column headers + val headerSet = mutableSetOf<String>() + messages.forEach { headerSet.add(it.getName()) } + val headers: MutableList<String> = headerSet.toList().sorted().toMutableList() + headers.add(0, "timestamp") + + // Step 2: Create a list with the current values for each column. Will be updated with each + // change. + val currentRow: MutableList<String> = MutableList(headers.size) { DEFAULT_COLUMN_VALUE } + + // Step 3: For each message, make the correct update to [currentRow] and save it to [rows]. + val columnIndices: Map<String, Int> = + headers.mapIndexed { index, headerName -> headerName to index }.toMap() + val allRows = mutableListOf<List<String>>() + + messages.forEach { + if (!it.hasData()) { + return@forEach + } + + val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(it.timestamp) + if (formattedTimestamp != currentRow[0]) { + // The timestamp has updated, so save the previous row and continue to the next row + allRows.add(currentRow.toList()) + currentRow[0] = formattedTimestamp + } + val columnIndex = columnIndices[it.getName()]!! + currentRow[columnIndex] = it.getVal() + } + // Add the last row + allRows.add(currentRow.toList()) + + // Step 4: Dump the rows + DumpsysTableLogger( + name, + headers, + allRows, + ) + .printTableData(pw) + } + + /** + * A private implementation of [TableRowLogger]. + * + * Used so that external clients can't modify [timestamp]. + */ + private class TableRowLoggerImpl( + var timestamp: Long, + var columnPrefix: String, + val tableLogBuffer: TableLogBuffer, + ) : TableRowLogger { + /** Logs a change to a string value. */ + override fun logChange(columnName: String, value: String?) { + tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value) + } + + /** Logs a change to a boolean value. */ + override fun logChange(columnName: String, value: Boolean) { + tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value) + } + + /** Logs a change to an int value. */ + override fun logChange(columnName: String, value: Int) { + tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value) + } + } +} + +val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) +private const val DEFAULT_COLUMN_VALUE = "UNKNOWN" diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt new file mode 100644 index 000000000000..f1f906f46d2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject + +@SysUISingleton +class TableLogBufferFactory +@Inject +constructor( + private val dumpManager: DumpManager, + private val systemClock: SystemClock, +) { + fun create( + name: String, + maxSize: Int, + ): TableLogBuffer { + val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock) + tableBuffer.registerDumpables(dumpManager) + return tableBuffer + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt new file mode 100644 index 000000000000..a7ba13b55ed8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +/** + * A class that logs a row to [TableLogBuffer]. + * + * Objects that implement [Diffable] will receive an instance of this class, and can log any changes + * to individual fields using the [logChange] methods. All logged changes will be associated with + * the same timestamp. + */ +interface TableRowLogger { + /** Logs a change to a string value. */ + fun logChange(columnName: String, value: String?) + + /** Logs a change to a boolean value. */ + fun logChange(columnName: String, value: Boolean) + + /** Logs a change to an int value. */ + fun logChange(columnName: String, value: Int) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index fcd1b8abefe4..0662fb3d52b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.pipeline.dagger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository @@ -32,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiReposito import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl import dagger.Binds import dagger.Module +import dagger.Provides @Module abstract class StatusBarPipelineModule { @@ -57,4 +61,15 @@ abstract class StatusBarPipelineModule { @Binds abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor + + @Module + companion object { + @JvmStatic + @Provides + @SysUISingleton + @WifiTableLog + fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("WifiTableLog", 100) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt new file mode 100644 index 000000000000..ac395a9d521b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.dagger + +import javax.inject.Qualifier + +/** Wifi logs in table format. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class WifiTableLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt index 062c3d1a4b10..8436b13d7038 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt @@ -17,12 +17,30 @@ package com.android.systemui.statusbar.pipeline.wifi.data.model import androidx.annotation.VisibleForTesting +import com.android.systemui.log.table.TableRowLogger +import com.android.systemui.log.table.Diffable /** Provides information about the current wifi network. */ -sealed class WifiNetworkModel { +sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { + /** A model representing that we have no active wifi network. */ object Inactive : WifiNetworkModel() { override fun toString() = "WifiNetwork.Inactive" + + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { + if (prevVal is Inactive) { + return + } + row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) + + if (prevVal is CarrierMerged) { + // The only difference between CarrierMerged and Inactive is the type + return + } + + // When changing from Active to Inactive, we need to log diffs to all the fields. + logDiffsFromActiveToNotActive(prevVal as Active, row) + } } /** @@ -33,6 +51,21 @@ sealed class WifiNetworkModel { */ object CarrierMerged : WifiNetworkModel() { override fun toString() = "WifiNetwork.CarrierMerged" + + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { + if (prevVal is CarrierMerged) { + return + } + row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) + + if (prevVal is Inactive) { + // The only difference between CarrierMerged and Inactive is the type. + return + } + + // When changing from Active to CarrierMerged, we need to log diffs to all the fields. + logDiffsFromActiveToNotActive(prevVal as Active, row) + } } /** Provides information about an active wifi network. */ @@ -76,6 +109,41 @@ sealed class WifiNetworkModel { } } + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { + if (prevVal !is Active) { + row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE) + } + + if (prevVal !is Active || prevVal.networkId != networkId) { + row.logChange(COL_NETWORK_ID, networkId) + } + if (prevVal !is Active || prevVal.isValidated != isValidated) { + row.logChange(COL_VALIDATED, isValidated) + } + if (prevVal !is Active || prevVal.level != level) { + row.logChange(COL_LEVEL, level ?: LEVEL_DEFAULT) + } + if (prevVal !is Active || prevVal.ssid != ssid) { + row.logChange(COL_SSID, ssid) + } + + // TODO(b/238425913): The passpoint-related values are frequently never used, so it + // would be great to not log them when they're not used. + if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) { + row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) + } + if (prevVal !is Active || + prevVal.isOnlineSignUpForPasspointAccessPoint != + isOnlineSignUpForPasspointAccessPoint) { + row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) + } + if (prevVal !is Active || + prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) { + row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) + } + } + + override fun toString(): String { // Only include the passpoint-related values in the string if we have them. (Most // networks won't have them so they'll be mostly clutter.) @@ -101,4 +169,37 @@ sealed class WifiNetworkModel { internal const val MAX_VALID_LEVEL = 4 } } + + internal fun logDiffsFromActiveToNotActive(prevActive: Active, row: TableRowLogger) { + row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) + row.logChange(COL_VALIDATED, false) + row.logChange(COL_LEVEL, LEVEL_DEFAULT) + row.logChange(COL_SSID, null) + + if (prevActive.isPasspointAccessPoint) { + row.logChange(COL_PASSPOINT_ACCESS_POINT, false) + } + if (prevActive.isOnlineSignUpForPasspointAccessPoint) { + row.logChange(COL_ONLINE_SIGN_UP, false) + } + if (prevActive.passpointProviderFriendlyName != null) { + row.logChange(COL_PASSPOINT_NAME, null) + } + } } + +const val TYPE_CARRIER_MERGED = "CarrierMerged" +const val TYPE_INACTIVE = "Inactive" +const val TYPE_ACTIVE = "Active" + +const val COL_NETWORK_TYPE = "type" +const val COL_NETWORK_ID = "networkId" +const val COL_VALIDATED = "isValidated" +const val COL_LEVEL = "level" +const val COL_SSID = "ssid" +const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" +const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" +const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" + +const val LEVEL_DEFAULT = -1 +const val NETWORK_ID_DEFAULT = -1 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 93448c1dee0e..a6635361cb3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -36,6 +36,9 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange @@ -82,6 +85,7 @@ class WifiRepositoryImpl @Inject constructor( broadcastDispatcher: BroadcastDispatcher, connectivityManager: ConnectivityManager, logger: ConnectivityPipelineLogger, + @WifiTableLog wifiTableLogBuffer: TableLogBuffer, @Main mainExecutor: Executor, @Application scope: CoroutineScope, wifiManager: WifiManager?, @@ -199,6 +203,12 @@ class WifiRepositoryImpl @Inject constructor( awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } + .distinctUntilChanged() + .logDiffsForTable( + wifiTableLogBuffer, + columnPrefix = "wifiNetwork", + initialValue = WIFI_NETWORK_DEFAULT, + ) // There will be multiple wifi icons in different places that will frequently // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that // new subscribes will get the latest value immediately upon subscription. Otherwise, the diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt new file mode 100644 index 000000000000..432764a7de8f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class TableChangeTest : SysuiTestCase() { + + @Test + fun setString_isString() { + val underTest = TableChange() + + underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName") + underTest.set("fakeValue") + + assertThat(underTest.hasData()).isTrue() + assertThat(underTest.getVal()).isEqualTo("fakeValue") + } + + @Test + fun setBoolean_isBoolean() { + val underTest = TableChange() + + underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName") + underTest.set(true) + + assertThat(underTest.hasData()).isTrue() + assertThat(underTest.getVal()).isEqualTo("true") + } + + @Test + fun setInt_isInt() { + val underTest = TableChange() + + underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName") + underTest.set(8900) + + assertThat(underTest.hasData()).isTrue() + assertThat(underTest.getVal()).isEqualTo("8900") + } + + @Test + fun setThenReset_isEmpty() { + val underTest = TableChange() + + underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName") + underTest.set(8900) + underTest.reset(timestamp = 0, columnPrefix = "prefix", columnName = "name") + + assertThat(underTest.hasData()).isFalse() + assertThat(underTest.getVal()).isEqualTo("null") + } + + @Test + fun getName_hasPrefix() { + val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName") + + assertThat(underTest.getName()).contains("fakePrefix") + assertThat(underTest.getName()).contains("fakeName") + } + + @Test + fun getName_noPrefix() { + val underTest = TableChange(columnPrefix = "", columnName = "fakeName") + + assertThat(underTest.getName()).contains("fakeName") + } + + @Test + fun resetThenSet_hasNewValue() { + val underTest = TableChange() + + underTest.reset(timestamp = 100, columnPrefix = "prefix", columnName = "original") + underTest.set("fakeValue") + underTest.reset(timestamp = 0, columnPrefix = "", columnName = "updated") + underTest.set(8900) + + assertThat(underTest.hasData()).isTrue() + assertThat(underTest.getName()).contains("updated") + assertThat(underTest.getName()).doesNotContain("prefix") + assertThat(underTest.getName()).doesNotContain("original") + assertThat(underTest.getVal()).isEqualTo("8900") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt new file mode 100644 index 000000000000..688c66ac80c9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.table + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test + +@SmallTest +class TableLogBufferTest : SysuiTestCase() { + private lateinit var underTest: TableLogBuffer + + private lateinit var systemClock: FakeSystemClock + private lateinit var outputWriter: StringWriter + + @Before + fun setup() { + systemClock = FakeSystemClock() + outputWriter = StringWriter() + + underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock) + } + + @Test(expected = IllegalArgumentException::class) + fun maxSizeZero_throwsException() { + TableLogBuffer(maxSize = 0, "name", systemClock) + } + + @Test + fun dumpChanges_strChange_logsFromNext() { + systemClock.setCurrentTimeMillis(100L) + + val prevDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("stringValChange", "prevStringVal") + } + } + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("stringValChange", "newStringVal") + } + } + + underTest.logDiffs("prefix", prevDiffable, nextDiffable) + + val dumpedString = dumpChanges() + + assertThat(dumpedString).contains("prefix") + assertThat(dumpedString).contains("stringValChange") + assertThat(dumpedString).contains("newStringVal") + assertThat(dumpedString).doesNotContain("prevStringVal") + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) + } + + @Test + fun dumpChanges_boolChange_logsFromNext() { + systemClock.setCurrentTimeMillis(100L) + + val prevDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("booleanValChange", false) + } + } + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("booleanValChange", true) + } + } + + underTest.logDiffs("prefix", prevDiffable, nextDiffable) + + val dumpedString = dumpChanges() + + assertThat(dumpedString).contains("prefix") + assertThat(dumpedString).contains("booleanValChange") + assertThat(dumpedString).contains("true") + assertThat(dumpedString).doesNotContain("false") + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) + } + + @Test + fun dumpChanges_intChange_logsFromNext() { + systemClock.setCurrentTimeMillis(100L) + + val prevDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("intValChange", 12345) + } + } + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("intValChange", 67890) + } + } + + underTest.logDiffs("prefix", prevDiffable, nextDiffable) + + val dumpedString = dumpChanges() + + assertThat(dumpedString).contains("prefix") + assertThat(dumpedString).contains("intValChange") + assertThat(dumpedString).contains("67890") + assertThat(dumpedString).doesNotContain("12345") + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) + } + + @Test + fun dumpChanges_noPrefix() { + systemClock.setCurrentTimeMillis(100L) + + val prevDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("booleanValChange", false) + } + } + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("booleanValChange", true) + } + } + + // WHEN there's a blank prefix + underTest.logDiffs("", prevDiffable, nextDiffable) + + val dumpedString = dumpChanges() + + // THEN the dump still works + assertThat(dumpedString).contains("booleanValChange") + assertThat(dumpedString).contains("true") + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) + } + + @Test + fun dumpChanges_multipleChangesForSameColumn_logs() { + lateinit var valToDump: String + + val diffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("valChange", valToDump) + } + } + + systemClock.setCurrentTimeMillis(12000L) + valToDump = "stateValue12" + underTest.logDiffs(columnPrefix = "", diffable, diffable) + + systemClock.setCurrentTimeMillis(20000L) + valToDump = "stateValue20" + underTest.logDiffs(columnPrefix = "", diffable, diffable) + + systemClock.setCurrentTimeMillis(40000L) + valToDump = "stateValue40" + underTest.logDiffs(columnPrefix = "", diffable, diffable) + + systemClock.setCurrentTimeMillis(45000L) + valToDump = "stateValue45" + underTest.logDiffs(columnPrefix = "", diffable, diffable) + + val dumpedString = dumpChanges() + + assertThat(dumpedString).contains("valChange") + assertThat(dumpedString).contains("stateValue12") + assertThat(dumpedString).contains("stateValue20") + assertThat(dumpedString).contains("stateValue40") + assertThat(dumpedString).contains("stateValue45") + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(12000L)) + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(20000L)) + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(40000L)) + assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(45000L)) + } + + @Test + fun dumpChanges_multipleChangesAtOnce_logs() { + systemClock.setCurrentTimeMillis(100L) + + val prevDiffable = object : TestDiffable() {} + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("status", "in progress") + row.logChange("connected", false) + } + } + + underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable) + + val dumpedString = dumpChanges() + + assertThat(dumpedString).contains("status") + assertThat(dumpedString).contains("in progress") + assertThat(dumpedString).contains("connected") + assertThat(dumpedString).contains("false") + } + + @Test + fun dumpChanges_rotatesIfBufferIsFull() { + lateinit var valToDump: String + + val prevDiffable = object : TestDiffable() {} + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("status", valToDump) + } + } + + for (i in 0 until MAX_SIZE + 3) { + valToDump = "testString[$i]" + underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable) + } + + val dumpedString = dumpChanges() + + assertThat(dumpedString).doesNotContain("testString[0]") + assertThat(dumpedString).doesNotContain("testString[1]") + assertThat(dumpedString).doesNotContain("testString[2]") + assertThat(dumpedString).contains("testString[3]") + assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]") + } + + private fun dumpChanges(): String { + underTest.dumpChanges(PrintWriter(outputWriter)) + return outputWriter.toString() + } + + private abstract class TestDiffable : Diffable<TestDiffable> { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {} + } +} + +private const val NAME = "TestTableBuffer" +private const val MAX_SIZE = 10 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt index 3d29d2b2c3c8..30fd308433e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.wifi.data.model import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL +import com.google.common.truth.Truth.assertThat import org.junit.Test @SmallTest @@ -48,6 +50,125 @@ class WifiNetworkModelTest : SysuiTestCase() { WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1) } + // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken + + @Test + fun logDiffs_inactiveToActive_logsAllActiveFields() { + val logger = TestLogger() + val activeNetwork = + WifiNetworkModel.Active( + networkId = 5, + isValidated = true, + level = 3, + ssid = "Test SSID" + ) + + activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) + + assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE)) + assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5")) + assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) + assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) + assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + } + @Test + fun logDiffs_activeToInactive_resetsAllActiveFields() { + val logger = TestLogger() + val activeNetwork = + WifiNetworkModel.Active( + networkId = 5, + isValidated = true, + level = 3, + ssid = "Test SSID" + ) + + WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger) + + assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE)) + assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString())) + assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) + assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) + assertThat(logger.changes).contains(Pair(COL_SSID, "null")) + } + + @Test + fun logDiffs_carrierMergedToActive_logsAllActiveFields() { + val logger = TestLogger() + val activeNetwork = + WifiNetworkModel.Active( + networkId = 5, + isValidated = true, + level = 3, + ssid = "Test SSID" + ) + + activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger) + + assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE)) + assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5")) + assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) + assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) + assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + } + @Test + fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() { + val logger = TestLogger() + val activeNetwork = + WifiNetworkModel.Active( + networkId = 5, + isValidated = true, + level = 3, + ssid = "Test SSID" + ) + + WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger) + + assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)) + assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString())) + assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) + assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) + assertThat(logger.changes).contains(Pair(COL_SSID, "null")) + } + + @Test + fun logDiffs_activeChangesLevel_onlyLevelLogged() { + val logger = TestLogger() + val prevActiveNetwork = + WifiNetworkModel.Active( + networkId = 5, + isValidated = true, + level = 3, + ssid = "Test SSID" + ) + val newActiveNetwork = + WifiNetworkModel.Active( + networkId = 5, + isValidated = true, + level = 2, + ssid = "Test SSID" + ) + + newActiveNetwork.logDiffs(prevActiveNetwork, logger) + + assertThat(logger.changes).isEqualTo(listOf(Pair(COL_LEVEL, "2"))) + } + + private class TestLogger : TableRowLogger { + val changes = mutableListOf<Pair<String, String>>() + + override fun logChange(columnName: String, value: String?) { + changes.add(Pair(columnName, value.toString())) + } + + override fun logChange(columnName: String, value: Int) { + changes.add(Pair(columnName, value.toString())) + } + + override fun logChange(columnName: String, value: Boolean) { + changes.add(Pair(columnName, value.toString())) + } + } + companion object { private const val NETWORK_ID = 2 } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt index a64a4bd2e57a..800f3c01c874 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -29,6 +29,7 @@ import android.net.wifi.WifiManager.TrafficStateCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT @@ -69,6 +70,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var tableLogger: TableLogBuffer @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var wifiManager: WifiManager private lateinit var executor: Executor @@ -804,6 +806,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { broadcastDispatcher, connectivityManager, logger, + tableLogger, executor, scope, wifiManagerToUse, |