diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index d9aa7212a71b7..c2a87649adef5 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -270,7 +270,7 @@ public class LogModule { @SysUISingleton @StatusBarConnectivityLog public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) { - return factory.create("StatusBarConnectivityLog", 64); + return factory.create("SbConnectivity", 64); } /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt index 6c02b0d44db3a..780a02da34103 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.pipeline -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo +import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo import kotlinx.coroutines.flow.StateFlow /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt index 8d69422c74275..99798f9b38d3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.pipeline import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilitiesRepo -import kotlinx.coroutines.CoroutineScope +import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilitiesRepo import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt index 1aae25058ba81..64c47f6791424 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt @@ -20,14 +20,18 @@ import android.content.Context import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo +import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel import javax.inject.Inject +import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** * A processor that transforms raw connectivity information that we get from callbacks and turns it @@ -42,12 +46,16 @@ import kotlinx.coroutines.flow.stateIn class ConnectivityInfoProcessor @Inject constructor( connectivityInfoCollector: ConnectivityInfoCollector, context: Context, + // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's + // scope so we only do work when there's UI that cares about it. @Application private val scope: CoroutineScope, - statusBarPipelineFlags: StatusBarPipelineFlags, + private val statusBarPipelineFlags: StatusBarPipelineFlags, + private val wifiViewModelProvider: Provider, ) : CoreStartable(context) { // Note: This flow will not start running until a client calls `collect` on it, which means that // [connectivityInfoCollector]'s flow will also not start anything until that `collect` call // happens. + // TODO(b/238425913): Delete this. val processedInfoFlow: Flow = if (!statusBarPipelineFlags.isNewPipelineEnabled()) emptyFlow() @@ -60,6 +68,14 @@ class ConnectivityInfoProcessor @Inject constructor( ) override fun start() { + if (!statusBarPipelineFlags.isNewPipelineEnabled()) { + return + } + // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can + // see the logs. + scope.launch { + wifiViewModelProvider.get().isActivityInVisible.collect { } + } } private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt deleted file mode 100644 index f88e9d67d25dc..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 - -import android.net.Network -import android.net.NetworkCapabilities -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.dagger.StatusBarConnectivityLog -import javax.inject.Inject - -@SysUISingleton -class ConnectivityPipelineLogger @Inject constructor( - @StatusBarConnectivityLog private val buffer: LogBuffer, -) { - fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - buffer.log( - TAG, - LogLevel.INFO, - { - int1 = network.getNetId() - str1 = networkCapabilities.toString() - }, - { - "onCapabilitiesChanged: net=$int1 capabilities=$str1" - } - ) - } - - fun logOnLost(network: Network) { - buffer.log( - TAG, - LogLevel.INFO, - { - int1 = network.getNetId() - }, - { - "onLost: net=$int1" - } - ) - } -} - -private const val TAG = "SbConnectivityPipeline" 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 c4e2b732f388c..7abe19e7bbe0a 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 @@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -37,4 +39,7 @@ abstract class StatusBarPipelineModule { abstract fun provideConnectivityInfoCollector( impl: ConnectivityInfoCollectorImpl ): ConnectivityInfoCollector + + @Binds + abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt new file mode 100644 index 0000000000000..a5fff5e65ebc2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -0,0 +1,106 @@ +/* + * 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.shared + +import android.net.Network +import android.net.NetworkCapabilities +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.StatusBarConnectivityLog +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onEach + +@SysUISingleton +class ConnectivityPipelineLogger @Inject constructor( + @StatusBarConnectivityLog private val buffer: LogBuffer, +) { + fun logInputChange(callbackName: String, changeInfo: String) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = callbackName + str2 = changeInfo + }, + { + "Input: $str1: $str2" + } + ) + } + + fun logOutputChange(outputParamName: String, changeInfo: String) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = outputParamName + str2 = changeInfo + }, + { + "Output: $str1: $str2" + } + ) + } + + fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = network.getNetId() + str1 = networkCapabilities.toString() + }, + { + "onCapabilitiesChanged: net=$int1 capabilities=$str1" + } + ) + } + + fun logOnLost(network: Network) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = network.getNetId() + }, + { + "onLost: net=$int1" + } + ) + } + + companion object { + const val SB_LOGGING_TAG = "SbConnectivity" + + /** + * Log a change in one of the **outputs** to the connectivity pipeline. + * + * @param prettyPrint an optional function to transform the value into a readable string. + * [toString] is used if no custom function is provided. + */ + fun Flow.logOutputChange( + logger: ConnectivityPipelineLogger, + outputParamName: String, + prettyPrint: (T) -> String = { it.toString() } + ): Flow { + return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt new file mode 100644 index 0000000000000..44c04968041e3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt @@ -0,0 +1,27 @@ +/* + * 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.wifi.data.model + +/** + * Provides information on the current wifi activity. + */ +data class WifiActivityModel( + /** True if the wifi has activity in (download). */ + val hasActivityIn: Boolean, + /** True if the wifi has activity out (upload). */ + val hasActivityOut: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt new file mode 100644 index 0000000000000..1b7332265b7a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt @@ -0,0 +1,29 @@ +/* + * 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.wifi.data.model + +/** Provides information about the current wifi state. */ +data class WifiModel( + /** See [android.net.wifi.WifiInfo.ssid]. */ + val ssid: String? = null, + /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ + val isPasspointAccessPoint: Boolean = false, + /** See [android.net.wifi.WifiInfo.isOsuAp]. */ + val isOnlineSignUpForPasspointAccessPoint: Boolean = false, + /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ + val passpointProviderFriendlyName: String? = null, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt similarity index 91% rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt index e5980c3693ee2..6c0a44524e3a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt @@ -16,7 +16,7 @@ @file:OptIn(ExperimentalCoroutinesApi::class) -package com.android.systemui.statusbar.pipeline.repository +package com.android.systemui.statusbar.pipeline.wifi.data.repository import android.annotation.SuppressLint import android.net.ConnectivityManager @@ -25,7 +25,7 @@ import android.net.NetworkCapabilities import android.net.NetworkRequest import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,7 +35,11 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.stateIn -/** Repository that contains all relevant [NetworkCapabilites] for the current networks */ +/** + * Repository that contains all relevant [NetworkCapabilities] for the current networks. + * + * TODO(b/238425913): Figure out how to merge this with [WifiRepository]. + */ @SysUISingleton class NetworkCapabilitiesRepo @Inject constructor( connectivityManager: ConnectivityManager, @@ -88,5 +92,3 @@ data class NetworkCapabilityInfo( val network: Network, val capabilities: NetworkCapabilities, ) - -private const val TAG = "ConnectivityRepository" 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 new file mode 100644 index 0000000000000..012dde5fb8f2c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -0,0 +1,103 @@ +/* + * 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.wifi.data.repository + +import android.net.wifi.WifiManager +import android.net.wifi.WifiManager.TrafficStateCallback +import android.util.Log +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +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.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** + * Provides data related to the wifi state. + */ +interface WifiRepository { + /** + * Observable for the current state of wifi; `null` when there is no active wifi. + */ + val wifiModel: Flow + + /** + * Observable for the current wifi network activity. + */ + val wifiActivity: Flow +} + +/** Real implementation of [WifiRepository]. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class WifiRepositoryImpl @Inject constructor( + wifiManager: WifiManager?, + @Main mainExecutor: Executor, + logger: ConnectivityPipelineLogger, +) : WifiRepository { + + // TODO(b/238425913): Actually implement the wifiModel flow. + override val wifiModel: Flow = flowOf(WifiModel(ssid = "AB")) + + override val wifiActivity: Flow = + if (wifiManager == null) { + Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback") + flowOf(ACTIVITY_DEFAULT) + } else { + conflatedCallbackFlow { + val callback = TrafficStateCallback { state -> + logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state)) + trySend(trafficStateToWifiActivityModel(state)) + } + + wifiManager.registerTrafficStateCallback(mainExecutor, callback) + trySend(ACTIVITY_DEFAULT) + + awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } + } + } + + companion object { + val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + + private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel { + return WifiActivityModel( + hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN || + state == TrafficStateCallback.DATA_ACTIVITY_INOUT, + hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT || + state == TrafficStateCallback.DATA_ACTIVITY_INOUT, + ) + } + + private fun prettyPrintActivity(activity: Int): String { + return when (activity) { + TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" + TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" + TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" + TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" + else -> "INVALID" + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt new file mode 100644 index 0000000000000..f705399af85d3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -0,0 +1,50 @@ +/* + * 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.wifi.domain.interactor + +import android.net.wifi.WifiManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +/** + * The business logic layer for the wifi icon. + * + * This interactor processes information from our data layer into information that the UI layer can + * use. + */ +@SysUISingleton +class WifiInteractor @Inject constructor( + repository: WifiRepository, +) { + private val ssid: Flow = repository.wifiModel.map { info -> + when { + info == null -> null + info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> + info.passpointProviderFriendlyName + info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid + else -> null + } + } + + val hasActivityIn: Flow = combine(repository.wifiActivity, ssid) { activity, ssid -> + activity.hasActivityIn && ssid != null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt new file mode 100644 index 0000000000000..a19d1bdd8e628 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt @@ -0,0 +1,49 @@ +/* + * 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.wifi.shared + +import android.content.Context +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG +import java.io.PrintWriter +import javax.inject.Inject + +/** + * An object storing constants that we use for calculating the wifi icon. Stored in a class for + * logging purposes. + */ +@SysUISingleton +class WifiConstants @Inject constructor( + context: Context, + dumpManager: DumpManager, +) : Dumpable { + init { + dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this) + } + + /** True if we should show the activityIn/activityOut icons and false otherwise. */ + val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity) + + override fun dump(pw: PrintWriter, args: Array) { + pw.apply { + println("shouldShowActivityConfig=$shouldShowActivityConfig") + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt new file mode 100644 index 0000000000000..b990eb7d91736 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -0,0 +1,45 @@ +/* + * 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.wifi.ui.viewmodel + +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor +import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** + * Models the UI state for the status bar wifi icon. + * + * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder. + */ +class WifiViewModel @Inject constructor( + private val constants: WifiConstants, + private val logger: ConnectivityPipelineLogger, + private val interactor: WifiInteractor, +) { + val isActivityInVisible: Flow + get() = + if (!constants.shouldShowActivityConfig) { + flowOf(false) + } else { + interactor.hasActivityIn + } + .logOutputChange(logger, "activityInVisible") +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt index 515a7c936f4d6..7b492cb7ddd17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt @@ -20,7 +20,7 @@ import android.net.NetworkCapabilities import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo +import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -59,6 +59,7 @@ class ConnectivityInfoProcessorTest : SysuiTestCase() { context, scope, statusBarPipelineFlags, + mock(), ) var mostRecentValue: ProcessedConnectivityInfo? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt similarity index 97% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt index 2915ae8dea88a..36be1be309d6d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline +package com.android.systemui.statusbar.pipeline.shared import android.net.Network import android.net.NetworkCapabilities diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt new file mode 100644 index 0000000000000..df389bc526646 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.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.statusbar.pipeline.wifi.data.repository + +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */ +class FakeWifiRepository : WifiRepository { + private val _wifiModel: MutableStateFlow = MutableStateFlow(null) + override val wifiModel: Flow = _wifiModel + + private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT) + override val wifiActivity: Flow = _wifiActivity + + fun setWifiModel(wifiModel: WifiModel?) { + _wifiModel.value = wifiModel + } + + fun setWifiActivity(activity: WifiActivityModel) { + _wifiActivity.value = activity + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt index 40f8fbfd9af2e..6edf76ce77c95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.repository +package com.android.systemui.statusbar.pipeline.wifi.data.repository import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback @@ -28,7 +28,7 @@ import android.net.NetworkRequest import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat 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 new file mode 100644 index 0000000000000..8b61364a2ac92 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -0,0 +1,175 @@ +/* + * 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.wifi.data.repository + +import android.net.wifi.WifiManager +import android.net.wifi.WifiManager.TrafficStateCallback +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class WifiRepositoryImplTest : SysuiTestCase() { + + private lateinit var underTest: WifiRepositoryImpl + + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var wifiManager: WifiManager + private lateinit var executor: Executor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + executor = FakeExecutor(FakeSystemClock()) + } + + @Test + fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) { + underTest = WifiRepositoryImpl( + wifiManager = null, + executor, + logger, + ) + + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isEqualTo(ACTIVITY_DEFAULT) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) { + underTest = WifiRepositoryImpl( + wifiManager, + executor, + logger, + ) + + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE) + + assertThat(latest).isEqualTo( + WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + ) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) { + underTest = WifiRepositoryImpl( + wifiManager, + executor, + logger, + ) + + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN) + + assertThat(latest).isEqualTo( + WifiActivityModel(hasActivityIn = true, hasActivityOut = false) + ) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) { + underTest = WifiRepositoryImpl( + wifiManager, + executor, + logger, + ) + + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT) + + assertThat(latest).isEqualTo( + WifiActivityModel(hasActivityIn = false, hasActivityOut = true) + ) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) { + underTest = WifiRepositoryImpl( + wifiManager, + executor, + logger, + ) + + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT) + + assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + + job.cancel() + } + + private fun getTrafficStateCallback(): TrafficStateCallback { + val callbackCaptor = argumentCaptor() + verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } +} + +private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt new file mode 100644 index 0000000000000..c52f347a605ae --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -0,0 +1,163 @@ +/* + * 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.wifi.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class WifiInteractorTest : SysuiTestCase() { + + private lateinit var underTest: WifiInteractor + + private lateinit var repository: FakeWifiRepository + + @Before + fun setUp() { + repository = FakeWifiRepository() + underTest = WifiInteractor(repository) + } + + @Test + fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) { + repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) { + repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) { + repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) { + repository.setWifiModel(WifiModel(ssid = "AB")) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) { + repository.setWifiModel(WifiModel(ssid = null)) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) { + repository.setWifiModel(WifiModel(ssid = "AB")) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + // Conduct a series of changes and verify we catch each of them in succession + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + assertThat(latest).isTrue() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true)) + yield() + assertThat(latest).isFalse() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + yield() + assertThat(latest).isTrue() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + assertThat(latest).isTrue() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false)) + yield() + assertThat(latest).isFalse() + + job.cancel() + } +} + +private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt new file mode 100644 index 0000000000000..e9259b071e0b9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -0,0 +1,124 @@ +/* + * 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.wifi.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor +import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class WifiViewModelTest : SysuiTestCase() { + + private lateinit var underTest: WifiViewModel + + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var constants: WifiConstants + private lateinit var repository: FakeWifiRepository + private lateinit var interactor: WifiInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + repository = FakeWifiRepository() + interactor = WifiInteractor(repository) + + underTest = WifiViewModel( + constants, + logger, + interactor + ) + + // Set up with a valid SSID + repository.setWifiModel(WifiModel(ssid = "AB")) + } + + @Test + fun activityInVisible_showActivityConfigFalse_receivesFalse() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(false) + + var latest: Boolean? = null + val job = underTest + .isActivityInVisible + .onEach { latest = it } + .launchIn(this) + + // Verify that on launch, we receive a false. + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(false) + + var latest: Boolean? = null + val job = underTest + .isActivityInVisible + .onEach { latest = it } + .launchIn(this) + + // Update the repo to have activityIn + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + + // Verify that we didn't update to activityIn=true (because our config is false) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun activityInVisible_showActivityConfigTrue_receivesUpdate() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + + var latest: Boolean? = null + val job = underTest + .isActivityInVisible + .onEach { latest = it } + .launchIn(this) + + // Update the repo to have activityIn + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + + // Verify that we updated to activityIn=true + assertThat(latest).isTrue() + + job.cancel() + } +} + +private val IMMEDIATE = Dispatchers.Main.immediate