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 index 88d8a86d39f2d..3a7ff167af18d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -133,7 +133,7 @@ class ConnectivityPipelineLogger @Inject constructor( * @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( + fun Flow.logOutputChange( logger: ConnectivityPipelineLogger, outputParamName: String, prettyPrint: (T) -> String = { it.toString() } 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 f41264fc570ff..6f20cbc004e1a 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,8 +36,8 @@ import com.android.systemui.dagger.qualifiers.Application 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.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope 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 index 952525d243f99..ce6003f0abd9b 100644 --- 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 @@ -22,9 +22,10 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map /** @@ -38,7 +39,11 @@ class WifiInteractor @Inject constructor( connectivityRepository: ConnectivityRepository, wifiRepository: WifiRepository, ) { - private val ssid: Flow = wifiRepository.wifiNetwork.map { info -> + /** + * The SSID (service set identifier) of the wifi network. Null if we don't have a network, or + * have a network but no valid SSID. + */ + val ssid: Flow = wifiRepository.wifiNetwork.map { info -> when (info) { is WifiNetworkModel.Inactive -> null is WifiNetworkModel.CarrierMerged -> null @@ -54,14 +59,11 @@ class WifiInteractor @Inject constructor( /** Our current wifi network. See [WifiNetworkModel]. */ val wifiNetwork: Flow = wifiRepository.wifiNetwork + /** Our current wifi activity. See [WifiActivityModel]. */ + val activity: StateFlow = wifiRepository.wifiActivity + /** True if we're configured to force-hide the wifi icon and false otherwise. */ val isForceHidden: Flow = connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.WIFI) } - - /** True if our wifi network has activity in (download), and false otherwise. */ - val hasActivityIn: Flow = - combine(wifiRepository.wifiActivity, ssid) { activity, ssid -> - activity.hasActivityIn && ssid != null - } } 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/shared/model/WifiActivityModel.kt similarity index 86% rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt index 44c04968041e3..574610605b4e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt @@ -14,11 +14,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.wifi.data.model +package com.android.systemui.statusbar.pipeline.wifi.shared.model -/** - * Provides information on the current wifi activity. - */ +/** Provides information on the current wifi activity. */ data class WifiActivityModel( /** True if the wifi has activity in (download). */ val hasActivityIn: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index 4fad3274d12f5..26667ab6d413c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -48,6 +48,9 @@ object WifiViewBinder { viewModel: WifiViewModel, ) { val iconView = view.requireViewById(R.id.wifi_signal) + val activityInView = view.requireViewById(R.id.wifi_in) + val activityOutView = view.requireViewById(R.id.wifi_out) + val activityContainerView = view.requireViewById(R.id.inout_container) view.isVisible = true iconView.isVisible = true @@ -61,20 +64,37 @@ object WifiViewBinder { // [ModernStatusBarWifiView.isIconVisible], which is what actually makes // the view GONE. view.isVisible = wifiIcon != null - wifiIcon?.let { - IconViewBinder.bind(wifiIcon, iconView) - } + wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) } } } launch { viewModel.tint.collect { tint -> - iconView.imageTintList = ColorStateList.valueOf(tint) + val tintList = ColorStateList.valueOf(tint) + iconView.imageTintList = tintList + activityInView.imageTintList = tintList + activityOutView.imageTintList = tintList + } + } + + launch { + viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible -> + activityInView.isVisible = visible + } + } + + launch { + viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible -> + activityOutView.isVisible = visible + } + } + + launch { + viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible -> + activityContainerView.isVisible = visible } } } } - - // TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon. } } 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 index 3c243ac908311..8197e89cb93ef 100644 --- 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 @@ -35,9 +35,11 @@ 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.domain.interactor.WifiInteractor import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -46,11 +48,11 @@ import kotlinx.coroutines.flow.map * Models the UI state for the status bar wifi icon. */ class WifiViewModel @Inject constructor( - statusBarPipelineFlags: StatusBarPipelineFlags, - private val constants: WifiConstants, + constants: WifiConstants, private val context: Context, - private val logger: ConnectivityPipelineLogger, - private val interactor: WifiInteractor, + logger: ConnectivityPipelineLogger, + interactor: WifiInteractor, + statusBarPipelineFlags: StatusBarPipelineFlags, ) { /** * The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon. @@ -109,17 +111,36 @@ class WifiViewModel @Inject constructor( } } - /** - * True if the activity in icon should be displayed and false otherwise. - */ - val isActivityInVisible: Flow - get() = - if (!constants.shouldShowActivityConfig) { - flowOf(false) - } else { - interactor.hasActivityIn + /** The wifi activity status. Null if we shouldn't display the activity status. */ + private val activity: Flow = + if (!constants.shouldShowActivityConfig) { + flowOf(null) + } else { + combine(interactor.activity, interactor.ssid) { activity, ssid -> + when (ssid) { + null -> null + else -> activity + } } - .logOutputChange(logger, "activityInVisible") + } + .distinctUntilChanged() + .logOutputChange(logger, "activity") + + /** True if the activity in view should be visible. */ + val isActivityInViewVisible: Flow = activity.map { it?.hasActivityIn == true } + + /** True if the activity out view should be visible. */ + val isActivityOutViewVisible: Flow = activity.map { it?.hasActivityOut == true } + + /** True if the activity container view should be visible. */ + val isActivityContainerVisible: Flow = + combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut -> + activityIn || activityOut + } + + // TODO(b/238425913): Update this class to use state flows instead. Right now, we have a ton of + // duplicate activity logs because the cold flows are getting duplicated for the three + // activityVisible flows. /** The tint that should be applied to the icon. */ val tint: Flow = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt index 36be1be309d6d..d3d8d542e0785 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt @@ -23,9 +23,15 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.runBlocking import org.junit.Test import org.mockito.Mockito import org.mockito.Mockito.mock @@ -64,12 +70,34 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() { assertThat(actualString).contains(expectedNetId) } - private val NET_1_ID = 100 - private val NET_1 = com.android.systemui.util.mockito.mock().also { - Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID) + @Test + fun logOutputChange_printsValuesAndNulls() = runBlocking(IMMEDIATE) { + val flow: Flow = flowOf(1, null, 3) + + val job = flow + .logOutputChange(logger, "testInts") + .launchIn(this) + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains("1") + assertThat(actualString).contains("null") + assertThat(actualString).contains("3") + + job.cancel() + } + + companion object { + private const val NET_1_ID = 100 + private val NET_1 = com.android.systemui.util.mockito.mock().also { + Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID) + } + private val NET_1_CAPS = NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .build() + private val IMMEDIATE = Dispatchers.Main.immediate } - private val NET_1_CAPS = NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - .build() } 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 index d59a25b8c5a9a..cd0f27a25b7e2 100644 --- 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 @@ -16,9 +16,9 @@ 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.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow 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 d070ba0e47beb..1878ce5b7e17b 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,10 +29,10 @@ 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.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor 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 index e896749d9a94a..622f20769f0df 100644 --- 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 @@ -16,13 +16,14 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor +import android.net.wifi.WifiManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,171 +51,105 @@ class WifiInteractorTest : SysuiTestCase() { } @Test - fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) - wifiRepository.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) { - wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) - wifiRepository.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) { - wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) - wifiRepository.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) { - wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) - wifiRepository.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) { - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null)) - wifiRepository.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_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) { + fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) { wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = true, hasActivityOut = true) - ) - var latest: Boolean? = null + var latest: String? = "default" val job = underTest - .hasActivityIn + .ssid .onEach { latest = it } .launchIn(this) - assertThat(latest).isFalse() + assertThat(latest).isNull() job.cancel() } @Test - fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) { + fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) { wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged) - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = true, hasActivityOut = true) - ) - var latest: Boolean? = null + var latest: String? = "default" val job = underTest - .hasActivityIn + .ssid .onEach { latest = it } .launchIn(this) - assertThat(latest).isFalse() + assertThat(latest).isNull() job.cancel() } @Test - fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) + fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork(WifiNetworkModel.Active( + networkId = 1, + isPasspointAccessPoint = true, + passpointProviderFriendlyName = "friendly", + )) - var latest: Boolean? = null + var latest: String? = null val job = underTest - .hasActivityIn - .onEach { latest = it } - .launchIn(this) + .ssid + .onEach { latest = it } + .launchIn(this) - // Conduct a series of changes and verify we catch each of them in succession - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = true, hasActivityOut = false) - ) - yield() - assertThat(latest).isTrue() + assertThat(latest).isEqualTo("friendly") - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = false, hasActivityOut = true) - ) - yield() - assertThat(latest).isFalse() + job.cancel() + } - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = true, hasActivityOut = true) - ) - yield() - assertThat(latest).isTrue() + @Test + fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork(WifiNetworkModel.Active( + networkId = 1, + isOnlineSignUpForPasspointAccessPoint = true, + passpointProviderFriendlyName = "friendly", + )) - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = true, hasActivityOut = false) - ) - yield() - assertThat(latest).isTrue() + var latest: String? = null + val job = underTest + .ssid + .onEach { latest = it } + .launchIn(this) - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = false, hasActivityOut = false) - ) - yield() - assertThat(latest).isFalse() + assertThat(latest).isEqualTo("friendly") + + job.cancel() + } + + @Test + fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork(WifiNetworkModel.Active( + networkId = 1, + ssid = WifiManager.UNKNOWN_SSID, + )) + + var latest: String? = "default" + val job = underTest + .ssid + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork(WifiNetworkModel.Active( + networkId = 1, + ssid = "MyAwesomeWifiNetwork", + )) + + var latest: String? = null + val job = underTest + .ssid + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isEqualTo("MyAwesomeWifiNetwork") job.cancel() } @@ -241,6 +176,32 @@ class WifiInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) { + var latest: WifiActivityModel? = null + val job = underTest + .activity + .onEach { latest = it } + .launchIn(this) + + val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity1) + yield() + assertThat(latest).isEqualTo(activity1) + + val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + wifiRepository.setWifiActivity(activity2) + yield() + assertThat(latest).isEqualTo(activity2) + + val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity3) + yield() + assertThat(latest).isEqualTo(activity3) + + job.cancel() + } + @Test fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) { connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) @@ -270,10 +231,6 @@ class WifiInteractorTest : SysuiTestCase() { job.cancel() } - - companion object { - val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB") - } } 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 index 43103a065e68c..f0ef9d043169c 100644 --- 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 @@ -29,11 +29,11 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel 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.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers @@ -67,14 +67,7 @@ class WifiViewModelTest : SysuiTestCase() { connectivityRepository = FakeConnectivityRepository() wifiRepository = FakeWifiRepository() interactor = WifiInteractor(connectivityRepository, wifiRepository) - - underTest = WifiViewModel( - statusBarPipelineFlags, - constants, - context, - logger, - interactor - ) + createAndSetViewModel() } @Test @@ -219,68 +212,299 @@ class WifiViewModelTest : SysuiTestCase() { } @Test - fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) { + fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) { whenever(constants.shouldShowActivityConfig).thenReturn(false) + createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .isActivityInVisible - .onEach { latest = it } + var activityIn: Boolean? = null + val activityInJob = underTest + .isActivityInViewVisible + .onEach { activityIn = it } .launchIn(this) - // Verify that on launch, we receive a false. - assertThat(latest).isFalse() + var activityOut: Boolean? = null + val activityOutJob = underTest + .isActivityOutViewVisible + .onEach { activityOut = it } + .launchIn(this) - job.cancel() + var activityContainer: Boolean? = null + val activityContainerJob = underTest + .isActivityContainerVisible + .onEach { activityContainer = it } + .launchIn(this) + + // Verify that on launch, we receive false. + assertThat(activityIn).isFalse() + assertThat(activityOut).isFalse() + assertThat(activityContainer).isFalse() + + activityInJob.cancel() + activityOutJob.cancel() + activityContainerJob.cancel() } @Test - fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) { + fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) { whenever(constants.shouldShowActivityConfig).thenReturn(false) + createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .isActivityInVisible - .onEach { latest = it } - .launchIn(this) + var activityIn: Boolean? = null + val activityInJob = underTest + .isActivityInViewVisible + .onEach { activityIn = it } + .launchIn(this) - // Update the repo to have activityIn - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = true, hasActivityOut = false) - ) + var activityOut: Boolean? = null + val activityOutJob = underTest + .isActivityOutViewVisible + .onEach { activityOut = it } + .launchIn(this) + + var activityContainer: Boolean? = null + val activityContainerJob = underTest + .isActivityContainerVisible + .onEach { activityContainer = it } + .launchIn(this) + + // WHEN we update the repo to have activity + val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) yield() - // Verify that we didn't update to activityIn=true (because our config is false) - assertThat(latest).isFalse() + // THEN we didn't update to the new activity (because our config is false) + assertThat(activityIn).isFalse() + assertThat(activityOut).isFalse() + assertThat(activityContainer).isFalse() - job.cancel() + activityInJob.cancel() + activityOutJob.cancel() + activityContainerJob.cancel() } @Test - fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) { + fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) { whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null)) + + var activityIn: Boolean? = null + val activityInJob = underTest + .isActivityInViewVisible + .onEach { activityIn = it } + .launchIn(this) + + var activityOut: Boolean? = null + val activityOutJob = underTest + .isActivityOutViewVisible + .onEach { activityOut = it } + .launchIn(this) + + var activityContainer: Boolean? = null + val activityContainerJob = underTest + .isActivityContainerVisible + .onEach { activityContainer = it } + .launchIn(this) + + // WHEN we update the repo to have activity + val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + // THEN we still output false because our network's SSID is null + assertThat(activityIn).isFalse() + assertThat(activityOut).isFalse() + assertThat(activityContainer).isFalse() + + activityInJob.cancel() + activityOutJob.cancel() + activityContainerJob.cancel() + } + + @Test + fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) var latest: Boolean? = null val job = underTest - .isActivityInVisible - .onEach { latest = it } - .launchIn(this) + .isActivityInViewVisible + .onEach { latest = it } + .launchIn(this) - // Update the repo to have activityIn - wifiRepository.setWifiActivity( - WifiActivityModel(hasActivityIn = true, hasActivityOut = false) - ) + val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) yield() - // Verify that we updated to activityIn=true assertThat(latest).isTrue() job.cancel() } + @Test + fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latest: Boolean? = null + val job = underTest + .isActivityInViewVisible + .onEach { latest = it } + .launchIn(this) + + val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latest: Boolean? = null + val job = underTest + .isActivityOutViewVisible + .onEach { latest = it } + .launchIn(this) + + val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latest: Boolean? = null + val job = underTest + .isActivityOutViewVisible + .onEach { latest = it } + .launchIn(this) + + val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latest: Boolean? = null + val job = underTest + .isActivityContainerVisible + .onEach { latest = it } + .launchIn(this) + + val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latest: Boolean? = null + val job = underTest + .isActivityContainerVisible + .onEach { latest = it } + .launchIn(this) + + val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latest: Boolean? = null + val job = underTest + .isActivityContainerVisible + .onEach { latest = it } + .launchIn(this) + + val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latest: Boolean? = null + val job = underTest + .isActivityContainerVisible + .onEach { latest = it } + .launchIn(this) + + val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + private fun createAndSetViewModel() { + // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow + // creations rely on certain config values that we mock out in individual tests. This method + // allows tests to create the view model only after those configs are correctly set up. + underTest = WifiViewModel( + constants, + context, + logger, + interactor, + statusBarPipelineFlags, + ) + } + private fun ContentDescription.getAsString(): String? { return when (this) { is ContentDescription.Loaded -> this.description