Merge "[SB Refactor] Implement the TunerService callback for the icon hide list and pipe it through to the wifi icon." into tm-qpr-dev
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 88eccb5..681dc6f 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
@@ -18,6 +18,8 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
 import dagger.Binds
@@ -34,5 +36,8 @@
     abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
 
     @Binds
+    abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
+
+    @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
index 2a89309..88d8a86 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
@@ -34,7 +34,7 @@
     /**
      * Logs a change in one of the **raw inputs** to the connectivity pipeline.
      */
-    fun logInputChange(callbackName: String, changeInfo: String) {
+    fun logInputChange(callbackName: String, changeInfo: String?) {
         buffer.log(
                 SB_LOGGING_TAG,
                 LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt
new file mode 100644
index 0000000..d52d0fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.data.model
+
+import android.content.Context
+import com.android.internal.R
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A container for all the different types of connectivity slots: wifi, mobile, etc.
+ */
+@SysUISingleton
+class ConnectivitySlots @Inject constructor(context: Context) {
+    private val airplaneSlot: String = context.getString(R.string.status_bar_airplane)
+    private val mobileSlot: String = context.getString(R.string.status_bar_mobile)
+    private val wifiSlot: String = context.getString(R.string.status_bar_wifi)
+    private val ethernetSlot: String = context.getString(R.string.status_bar_ethernet)
+
+    private val slotByName: Map<String, ConnectivitySlot> = mapOf(
+        airplaneSlot to ConnectivitySlot.AIRPLANE,
+        mobileSlot to ConnectivitySlot.MOBILE,
+        wifiSlot to ConnectivitySlot.WIFI,
+        ethernetSlot to ConnectivitySlot.ETHERNET
+    )
+
+    /**
+     * Given a string name of a slot, returns the instance of [ConnectivitySlot] that it corresponds
+     * to, or null if we couldn't find that slot name.
+     */
+    fun getSlotFromName(slotName: String): ConnectivitySlot? {
+        return slotByName[slotName]
+    }
+}
+
+/** The different types of connectivity slots. */
+enum class ConnectivitySlot {
+    AIRPLANE,
+    ETHERNET,
+    MOBILE,
+    WIFI,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
new file mode 100644
index 0000000..6b1750d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.data.repository
+
+import android.content.Context
+import androidx.annotation.ArrayRes
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.phone.StatusBarIconController
+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.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
+import com.android.systemui.tuner.TunerService
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides data related to the connectivity state that needs to be shared across multiple different
+ * types of connectivity (wifi, mobile, ethernet, etc.)
+ */
+interface ConnectivityRepository {
+    /**
+     * Observable for the current set of connectivity icons that should be force-hidden.
+     */
+    val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>>
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class ConnectivityRepositoryImpl @Inject constructor(
+    private val connectivitySlots: ConnectivitySlots,
+    context: Context,
+    dumpManager: DumpManager,
+    logger: ConnectivityPipelineLogger,
+    @Application scope: CoroutineScope,
+    tunerService: TunerService,
+) : ConnectivityRepository, Dumpable {
+    init {
+        dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityRepository", this)
+    }
+
+    // The default set of hidden icons to use if we don't get any from [TunerService].
+    private val defaultHiddenIcons: Set<ConnectivitySlot> =
+            context.resources.getStringArray(DEFAULT_HIDDEN_ICONS_RESOURCE)
+                .asList()
+                .toSlotSet(connectivitySlots)
+
+    override val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>> = conflatedCallbackFlow {
+        val callback = object : TunerService.Tunable {
+            override fun onTuningChanged(key: String, newHideList: String?) {
+                if (key != HIDDEN_ICONS_TUNABLE_KEY) {
+                    return
+                }
+                logger.logInputChange("onTuningChanged", newHideList)
+
+                val outputList = newHideList?.split(",")?.toSlotSet(connectivitySlots)
+                    ?: defaultHiddenIcons
+                trySend(outputList)
+            }
+        }
+        tunerService.addTunable(callback, HIDDEN_ICONS_TUNABLE_KEY)
+
+        awaitClose { tunerService.removeTunable(callback) }
+    }
+        .stateIn(
+            scope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = defaultHiddenIcons
+        )
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.apply {
+            println("defaultHiddenIcons=$defaultHiddenIcons")
+        }
+    }
+
+    companion object {
+        @VisibleForTesting
+        internal const val HIDDEN_ICONS_TUNABLE_KEY = StatusBarIconController.ICON_HIDE_LIST
+        @VisibleForTesting
+        @ArrayRes
+        internal val DEFAULT_HIDDEN_ICONS_RESOURCE = R.array.config_statusBarIconsToExclude
+
+        /** Converts a list of string slot names to a set of [ConnectivitySlot] instances. */
+        private fun List<String>.toSlotSet(
+            connectivitySlots: ConnectivitySlots
+        ): Set<ConnectivitySlot> {
+            return this
+                .filter { it.isNotBlank() }
+                .mapNotNull { connectivitySlots.getSlotFromName(it) }
+                .toSet()
+        }
+    }
+}
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 afe19af..952525d 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
@@ -18,6 +18,8 @@
 
 import android.net.wifi.WifiManager
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+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 javax.inject.Inject
@@ -33,9 +35,10 @@
  */
 @SysUISingleton
 class WifiInteractor @Inject constructor(
-    repository: WifiRepository,
+    connectivityRepository: ConnectivityRepository,
+    wifiRepository: WifiRepository,
 ) {
-    private val ssid: Flow<String?> = repository.wifiNetwork.map { info ->
+    private val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
         when (info) {
             is WifiNetworkModel.Inactive -> null
             is WifiNetworkModel.CarrierMerged -> null
@@ -49,10 +52,16 @@
     }
 
     /** Our current wifi network. See [WifiNetworkModel]. */
-    val wifiNetwork: Flow<WifiNetworkModel> = repository.wifiNetwork
+    val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
+
+    /** True if we're configured to force-hide the wifi icon and false otherwise. */
+    val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
+        it.contains(ConnectivitySlot.WIFI)
+    }
 
     /** True if our wifi network has activity in (download), and false otherwise. */
-    val hasActivityIn: Flow<Boolean> = combine(repository.wifiActivity, ssid) { activity, ssid ->
+    val hasActivityIn: Flow<Boolean> =
+        combine(wifiRepository.wifiActivity, ssid) { activity, ssid ->
             activity.hasActivityIn && ssid != null
         }
 }
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 7607ddf..4fad327 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
@@ -24,6 +24,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
+import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
@@ -54,14 +55,15 @@
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
-                    viewModel.wifiIconResId.distinctUntilChanged().collect { iconResId ->
-                        iconView.setImageDrawable(
-                            if (iconResId != null && iconResId > 0) {
-                                iconView.context.getDrawable(iconResId)
-                            } else {
-                                null
-                            }
-                        )
+                    viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
+                        // TODO(b/238425913): Right now, if !isVisible, there's just an empty space
+                        //  where the wifi icon would be. We need to pipe isVisible through to
+                        //   [ModernStatusBarWifiView.isIconVisible], which is what actually makes
+                        //   the view GONE.
+                        view.isVisible = wifiIcon != null
+                        wifiIcon?.let {
+                            IconViewBinder.bind(wifiIcon, iconView)
+                        }
                     }
                 }
 
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 4fdcc44..1987528 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
@@ -18,6 +18,7 @@
 
 import android.graphics.Color
 import androidx.annotation.DrawableRes
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -29,6 +30,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -46,7 +48,7 @@
      * The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon.
      */
     @DrawableRes
-    val wifiIconResId: Flow<Int?> = interactor.wifiNetwork.map {
+    private val iconResId: Flow<Int?> = interactor.wifiNetwork.map {
         when (it) {
             is WifiNetworkModel.CarrierMerged -> null
             is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
@@ -59,6 +61,24 @@
         }
     }
 
+    /**
+     * The wifi icon that should be displayed. Null if we shouldn't display any icon.
+     */
+    val wifiIcon: Flow<Icon?> = combine(
+            interactor.isForceHidden,
+            iconResId
+        ) { isForceHidden, iconResId ->
+            when {
+                isForceHidden ||
+                    iconResId == null ||
+                    iconResId <= 0 -> null
+                else -> Icon.Resource(iconResId)
+            }
+        }
+
+    /**
+     * True if the activity in icon should be displayed and false otherwise.
+     */
     val isActivityInVisible: Flow<Boolean>
         get() =
             if (!constants.shouldShowActivityConfig) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
new file mode 100644
index 0000000..6dbee2f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -0,0 +1,295 @@
+/*
+ * 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.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+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.model.ConnectivitySlots
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class ConnectivityRepositoryImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: ConnectivityRepositoryImpl
+
+    @Mock private lateinit var connectivitySlots: ConnectivitySlots
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    private lateinit var scope: CoroutineScope
+    @Mock private lateinit var tunerService: TunerService
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        scope = CoroutineScope(IMMEDIATE)
+
+        underTest = ConnectivityRepositoryImpl(
+            connectivitySlots,
+            context,
+            dumpManager,
+            logger,
+            scope,
+            tunerService,
+        )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
+        setUpEthernetWifiMobileSlotNames()
+        context.getOrCreateTestableResources().addOverride(
+            DEFAULT_HIDDEN_ICONS_RESOURCE,
+            arrayOf(SLOT_WIFI, SLOT_ETHERNET)
+        )
+        // Re-create our [ConnectivityRepositoryImpl], since it fetches
+        // config_statusBarIconsToExclude when it's first constructed
+        underTest = ConnectivityRepositoryImpl(
+            connectivitySlots,
+            context,
+            dumpManager,
+            logger,
+            scope,
+            tunerService,
+        )
+
+        var latest: Set<ConnectivitySlot>? = null
+        val job = underTest
+            .forceHiddenSlots
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).containsExactly(ConnectivitySlot.ETHERNET, ConnectivitySlot.WIFI)
+
+        job.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_slotNamesAdded_flowHasSlots() = runBlocking(IMMEDIATE) {
+        setUpEthernetWifiMobileSlotNames()
+
+        var latest: Set<ConnectivitySlot>? = null
+        val job = underTest
+            .forceHiddenSlots
+            .onEach { latest = it }
+            .launchIn(this)
+
+        getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
+
+        assertThat(latest).containsExactly(ConnectivitySlot.MOBILE)
+
+        job.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_wrongKey_doesNotUpdate() = runBlocking(IMMEDIATE) {
+        setUpEthernetWifiMobileSlotNames()
+
+        var latest: Set<ConnectivitySlot>? = null
+        val job = underTest
+            .forceHiddenSlots
+            .onEach { latest = it }
+            .launchIn(this)
+
+        getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
+
+        // WHEN onTuningChanged with the wrong key
+        getTunable().onTuningChanged("wrongKey", SLOT_WIFI)
+        yield()
+
+        // THEN we didn't update our value and still have the old one
+        assertThat(latest).containsExactly(ConnectivitySlot.MOBILE)
+
+        job.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_slotNamesAddedThenNull_flowHasDefault() = runBlocking(IMMEDIATE) {
+        setUpEthernetWifiMobileSlotNames()
+        context.getOrCreateTestableResources().addOverride(
+            DEFAULT_HIDDEN_ICONS_RESOURCE,
+            arrayOf(SLOT_WIFI, SLOT_ETHERNET)
+        )
+        // Re-create our [ConnectivityRepositoryImpl], since it fetches
+        // config_statusBarIconsToExclude when it's first constructed
+        underTest = ConnectivityRepositoryImpl(
+            connectivitySlots,
+            context,
+            dumpManager,
+            logger,
+            scope,
+            tunerService,
+        )
+
+        var latest: Set<ConnectivitySlot>? = null
+        val job = underTest
+            .forceHiddenSlots
+            .onEach { latest = it }
+            .launchIn(this)
+
+        // First, update the slots
+        getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, SLOT_MOBILE)
+        assertThat(latest).containsExactly(ConnectivitySlot.MOBILE)
+
+        // WHEN we update to a null value
+        getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, null)
+        yield()
+
+        // THEN we go back to our default value
+        assertThat(latest).containsExactly(ConnectivitySlot.ETHERNET, ConnectivitySlot.WIFI)
+
+        job.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_someInvalidSlotNames_flowHasValidSlotsOnly() = runBlocking(IMMEDIATE) {
+        var latest: Set<ConnectivitySlot>? = null
+        val job = underTest
+            .forceHiddenSlots
+            .onEach { latest = it }
+            .launchIn(this)
+
+        whenever(connectivitySlots.getSlotFromName(SLOT_WIFI))
+            .thenReturn(ConnectivitySlot.WIFI)
+        whenever(connectivitySlots.getSlotFromName(SLOT_MOBILE)).thenReturn(null)
+
+        getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_WIFI,$SLOT_MOBILE")
+
+        assertThat(latest).containsExactly(ConnectivitySlot.WIFI)
+
+        job.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_someEmptySlotNames_flowHasValidSlotsOnly() = runBlocking(IMMEDIATE) {
+        setUpEthernetWifiMobileSlotNames()
+
+        var latest: Set<ConnectivitySlot>? = null
+        val job = underTest
+            .forceHiddenSlots
+            .onEach { latest = it }
+            .launchIn(this)
+
+        // WHEN there's empty and blank slot names
+        getTunable().onTuningChanged(
+            HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_MOBILE,  ,,$SLOT_WIFI"
+        )
+
+        // THEN we skip that slot but still process the other ones
+        assertThat(latest).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.MOBILE)
+
+        job.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_allInvalidOrEmptySlotNames_flowHasEmpty() = runBlocking(IMMEDIATE) {
+        var latest: Set<ConnectivitySlot>? = null
+        val job = underTest
+            .forceHiddenSlots
+            .onEach { latest = it }
+            .launchIn(this)
+
+        whenever(connectivitySlots.getSlotFromName(SLOT_WIFI)).thenReturn(null)
+        whenever(connectivitySlots.getSlotFromName(SLOT_ETHERNET)).thenReturn(null)
+        whenever(connectivitySlots.getSlotFromName(SLOT_MOBILE)).thenReturn(null)
+
+        getTunable().onTuningChanged(
+            HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_MOBILE,,$SLOT_WIFI,$SLOT_ETHERNET,,,"
+        )
+
+        assertThat(latest).isEmpty()
+
+        job.cancel()
+    }
+
+    @Test
+    fun forceHiddenSlots_newSubscriberGetsCurrentValue() = runBlocking(IMMEDIATE) {
+        setUpEthernetWifiMobileSlotNames()
+
+        var latest1: Set<ConnectivitySlot>? = null
+        val job1 = underTest
+            .forceHiddenSlots
+            .onEach { latest1 = it }
+            .launchIn(this)
+
+        getTunable().onTuningChanged(HIDDEN_ICONS_TUNABLE_KEY, "$SLOT_WIFI,$SLOT_ETHERNET")
+
+        assertThat(latest1).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.ETHERNET)
+
+        // WHEN we add a second subscriber after having already emitted a value
+        var latest2: Set<ConnectivitySlot>? = null
+        val job2 = underTest
+            .forceHiddenSlots
+            .onEach { latest2 = it }
+            .launchIn(this)
+
+        // THEN the second subscribe receives the already-emitted value
+        assertThat(latest2).containsExactly(ConnectivitySlot.WIFI, ConnectivitySlot.ETHERNET)
+
+        job1.cancel()
+        job2.cancel()
+    }
+
+    private fun getTunable(): TunerService.Tunable {
+        val callbackCaptor = argumentCaptor<TunerService.Tunable>()
+        Mockito.verify(tunerService).addTunable(callbackCaptor.capture(), any())
+        return callbackCaptor.value!!
+    }
+
+    private fun setUpEthernetWifiMobileSlotNames() {
+        whenever(connectivitySlots.getSlotFromName(SLOT_ETHERNET))
+            .thenReturn(ConnectivitySlot.ETHERNET)
+        whenever(connectivitySlots.getSlotFromName(SLOT_WIFI))
+            .thenReturn(ConnectivitySlot.WIFI)
+        whenever(connectivitySlots.getSlotFromName(SLOT_MOBILE))
+            .thenReturn(ConnectivitySlot.MOBILE)
+    }
+
+    companion object {
+        private const val SLOT_ETHERNET = "ethernet"
+        private const val SLOT_WIFI = "wifi"
+        private const val SLOT_MOBILE = "mobile"
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
new file mode 100644
index 0000000..bd70034
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.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.statusbar.pipeline.shared.data.repository
+
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake implementation of [ConnectivityRepository] exposing set methods for all the flows. */
+class FakeConnectivityRepository : ConnectivityRepository {
+    private val _forceHiddenIcons: MutableStateFlow<Set<ConnectivitySlot>> =
+        MutableStateFlow(emptySet())
+    override val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>> = _forceHiddenIcons
+
+    fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
+        _forceHiddenIcons.value = hiddenIcons
+    }
+}
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 9d8b4bc..e896749 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
@@ -18,6 +18,8 @@
 
 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
@@ -37,18 +39,22 @@
 
     private lateinit var underTest: WifiInteractor
 
-    private lateinit var repository: FakeWifiRepository
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+    private lateinit var wifiRepository: FakeWifiRepository
 
     @Before
     fun setUp() {
-        repository = FakeWifiRepository()
-        underTest = WifiInteractor(repository)
+        connectivityRepository = FakeConnectivityRepository()
+        wifiRepository = FakeWifiRepository()
+        underTest = WifiInteractor(connectivityRepository, wifiRepository)
     }
 
     @Test
     fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        )
 
         var latest: Boolean? = null
         val job = underTest
@@ -63,8 +69,10 @@
 
     @Test
     fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        )
 
         var latest: Boolean? = null
         val job = underTest
@@ -79,8 +87,10 @@
 
     @Test
     fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        )
 
         var latest: Boolean? = null
         val job = underTest
@@ -95,8 +105,10 @@
 
     @Test
     fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        )
 
         var latest: Boolean? = null
         val job = underTest
@@ -111,8 +123,10 @@
 
     @Test
     fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        )
 
         var latest: Boolean? = null
         val job = underTest
@@ -127,8 +141,10 @@
 
     @Test
     fun hasActivityIn_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(WifiNetworkModel.Inactive)
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        )
 
         var latest: Boolean? = null
         val job = underTest
@@ -143,8 +159,10 @@
 
     @Test
     fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+        wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        )
 
         var latest: Boolean? = null
         val job = underTest
@@ -159,7 +177,7 @@
 
     @Test
     fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
 
         var latest: Boolean? = null
         val job = underTest
@@ -168,23 +186,33 @@
                 .launchIn(this)
 
         // Conduct a series of changes and verify we catch each of them in succession
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        )
         yield()
         assertThat(latest).isTrue()
 
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        )
         yield()
         assertThat(latest).isFalse()
 
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        )
         yield()
         assertThat(latest).isTrue()
 
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        )
         yield()
         assertThat(latest).isTrue()
 
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        )
         yield()
         assertThat(latest).isFalse()
 
@@ -200,7 +228,7 @@
             ssid = "AB",
             passpointProviderFriendlyName = "friendly"
         )
-        repository.setWifiNetwork(wifiNetwork)
+        wifiRepository.setWifiNetwork(wifiNetwork)
 
         var latest: WifiNetworkModel? = null
         val job = underTest
@@ -213,6 +241,36 @@
         job.cancel()
     }
 
+    @Test
+    fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
+        connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+
+        var latest: Boolean? = null
+        val job = underTest
+            .isForceHidden
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() = runBlocking(IMMEDIATE) {
+        connectivityRepository.setForceHiddenIcons(setOf())
+
+        var latest: Boolean? = null
+        val job = underTest
+            .isForceHidden
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
     companion object {
         val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
     }
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 f0a775b..6c6d9e1 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
@@ -18,11 +18,14 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
 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
@@ -50,14 +53,16 @@
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var constants: WifiConstants
-    private lateinit var repository: FakeWifiRepository
+    private lateinit var connectivityRepository: FakeConnectivityRepository
+    private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        repository = FakeWifiRepository()
-        interactor = WifiInteractor(repository)
+        connectivityRepository = FakeConnectivityRepository()
+        wifiRepository = FakeWifiRepository()
+        interactor = WifiInteractor(connectivityRepository, wifiRepository)
 
         underTest = WifiViewModel(
             statusBarPipelineFlags,
@@ -68,42 +73,13 @@
     }
 
     @Test
-    fun wifiIconResId_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(WifiNetworkModel.Inactive)
+    fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
+        connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
 
-        var latest: Int? = null
+        var latest: Icon? = null
         val job = underTest
-                .wifiIconResId
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isEqualTo(WIFI_NO_NETWORK)
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIconResId_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
-
-        var latest: Int? = null
-        val job = underTest
-                .wifiIconResId
-                .onEach { latest = it }
-                .launchIn(this)
-
-        assertThat(latest).isNull()
-
-        job.cancel()
-    }
-
-    @Test
-    fun wifiIconResId_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
-        repository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
-
-        var latest: Int? = null
-        val job = underTest
-            .wifiIconResId
+            .wifiIcon
             .onEach { latest = it }
             .launchIn(this)
 
@@ -113,10 +89,72 @@
     }
 
     @Test
-    fun wifiIconResId_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
+    fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
+        connectivityRepository.setForceHiddenIcons(setOf())
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
+
+        var latest: Icon? = null
+        val job = underTest
+            .wifiIcon
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIcon_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+        var latest: Icon? = null
+        val job = underTest
+                .wifiIcon
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
+        assertThat((latest as Icon.Resource).res).isEqualTo(WIFI_NO_NETWORK)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+
+        var latest: Icon? = null
+        val job = underTest
+            .wifiIcon
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIcon_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
+        wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
+
+        var latest: Icon? = null
+        val job = underTest
+            .wifiIcon
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isNull()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
         val level = 1
 
-        repository.setWifiNetwork(
+        wifiRepository.setWifiNetwork(
                 WifiNetworkModel.Active(
                         NETWORK_ID,
                         isValidated = true,
@@ -124,22 +162,23 @@
                 )
         )
 
-        var latest: Int? = null
+        var latest: Icon? = null
         val job = underTest
-                .wifiIconResId
-                .onEach { latest = it }
-                .launchIn(this)
+            .wifiIcon
+            .onEach { latest = it }
+            .launchIn(this)
 
-        assertThat(latest).isEqualTo(WIFI_FULL_ICONS[level])
+        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
+        assertThat((latest as Icon.Resource).res).isEqualTo(WIFI_FULL_ICONS[level])
 
         job.cancel()
     }
 
     @Test
-    fun wifiIconResId_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
+    fun wifiIcon_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
         val level = 4
 
-        repository.setWifiNetwork(
+        wifiRepository.setWifiNetwork(
                 WifiNetworkModel.Active(
                         NETWORK_ID,
                         isValidated = false,
@@ -147,13 +186,14 @@
                 )
         )
 
-        var latest: Int? = null
+        var latest: Icon? = null
         val job = underTest
-                .wifiIconResId
-                .onEach { latest = it }
-                .launchIn(this)
+            .wifiIcon
+            .onEach { latest = it }
+            .launchIn(this)
 
-        assertThat(latest).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
+        assertThat(latest).isInstanceOf(Icon.Resource::class.java)
+        assertThat((latest as Icon.Resource).res).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
 
         job.cancel()
     }
@@ -161,7 +201,7 @@
     @Test
     fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
         whenever(constants.shouldShowActivityConfig).thenReturn(false)
-        repository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
         var latest: Boolean? = null
         val job = underTest
@@ -178,7 +218,7 @@
     @Test
     fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
         whenever(constants.shouldShowActivityConfig).thenReturn(false)
-        repository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
         var latest: Boolean? = null
         val job = underTest
@@ -187,7 +227,9 @@
                 .launchIn(this)
 
         // Update the repo to have activityIn
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        )
         yield()
 
         // Verify that we didn't update to activityIn=true (because our config is false)
@@ -199,7 +241,7 @@
     @Test
     fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) {
         whenever(constants.shouldShowActivityConfig).thenReturn(true)
-        repository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+        wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
         var latest: Boolean? = null
         val job = underTest
@@ -208,7 +250,9 @@
                 .launchIn(this)
 
         // Update the repo to have activityIn
-        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        wifiRepository.setWifiActivity(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        )
         yield()
 
         // Verify that we updated to activityIn=true