[kairos] Tests for KairosAdapters

Flag: com.android.systemui.status_bar_mobile_icon_kairos
Bug: 383172066
Test: atest
Change-Id: Icd4206176c8ad92467eb61bb7b33ace534ac27a7
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7f45443..2b17ae4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -220,6 +220,7 @@
         "tests/src/**/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt",
         "tests/src/**/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt",
@@ -356,7 +357,9 @@
         "tests/src/**/systemui/qs/tiles/AlarmTileTest.kt",
         "tests/src/**/systemui/qs/tiles/BluetoothTileTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosAdapterTest.kt
new file mode 100644
index 0000000..3cf787d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosAdapterTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2025 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.mobile.data.repository.prod
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.activated
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarrierMergedConnectionRepositoryKairosAdapterTest :
+    CarrierMergedConnectionRepositoryTestBase() {
+
+    var job: Job? = null
+    val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+    override fun recreateRepo(): MobileConnectionRepositoryKairosAdapter {
+        lateinit var adapter: MobileConnectionRepositoryKairosAdapter
+        job?.cancel()
+        Mockito.clearInvocations(telephonyManager)
+        job =
+            testScope.backgroundScope.launch {
+                kairosNetwork.activateSpec {
+                    val repo = activated {
+                        CarrierMergedConnectionRepositoryKairos(
+                            SUB_ID,
+                            logger,
+                            telephonyManager,
+                            wifiRepository,
+                            isInEcmMode = stateOf(false),
+                        )
+                    }
+                    adapter =
+                        MobileConnectionRepositoryKairosAdapter(
+                            repo,
+                            SystemUiCarrierConfig(SUB_ID, testCarrierConfig()),
+                        )
+                    Unit
+                }
+            }
+        testScope.runCurrent() // ensure the lateinit is set
+        return adapter
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index 8e55f2e..8a6829c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -43,16 +44,30 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
+class CarrierMergedConnectionRepositoryTest : CarrierMergedConnectionRepositoryTestBase() {
+    override fun recreateRepo() =
+        CarrierMergedConnectionRepository(
+            SUB_ID,
+            logger,
+            telephonyManager,
+            testScope.backgroundScope.coroutineContext,
+            testScope.backgroundScope,
+            wifiRepository,
+        )
+}
 
-    private lateinit var underTest: CarrierMergedConnectionRepository
+abstract class CarrierMergedConnectionRepositoryTestBase : SysuiTestCase() {
 
-    private lateinit var wifiRepository: FakeWifiRepository
-    @Mock private lateinit var logger: TableLogBuffer
-    @Mock private lateinit var telephonyManager: TelephonyManager
+    protected lateinit var underTest: MobileConnectionRepository
 
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    protected lateinit var wifiRepository: FakeWifiRepository
+    @Mock protected lateinit var logger: TableLogBuffer
+    @Mock protected lateinit var telephonyManager: TelephonyManager
+
+    protected val testDispatcher = UnconfinedTestDispatcher()
+    protected val testScope = TestScope(testDispatcher)
+
+    abstract fun recreateRepo(): MobileConnectionRepository
 
     @Before
     fun setUp() {
@@ -62,15 +77,7 @@
 
         wifiRepository = FakeWifiRepository()
 
-        underTest =
-            CarrierMergedConnectionRepository(
-                SUB_ID,
-                logger,
-                telephonyManager,
-                testScope.backgroundScope.coroutineContext,
-                testScope.backgroundScope,
-                wifiRepository,
-            )
+        underTest = recreateRepo()
     }
 
     @Test
@@ -121,10 +128,7 @@
             wifiRepository.setIsWifiDefault(true)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged.of(
-                    subscriptionId = SUB_ID,
-                    level = 3,
-                )
+                WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
             )
 
             assertThat(latest).isEqualTo(3)
@@ -141,26 +145,17 @@
             wifiRepository.setIsWifiEnabled(true)
             wifiRepository.setIsWifiDefault(true)
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged.of(
-                    subscriptionId = SUB_ID,
-                    level = 3,
-                )
+                WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
             )
             wifiRepository.setWifiActivity(
-                DataActivityModel(
-                    hasActivityIn = true,
-                    hasActivityOut = false,
-                )
+                DataActivityModel(hasActivityIn = true, hasActivityOut = false)
             )
 
             assertThat(latest!!.hasActivityIn).isTrue()
             assertThat(latest!!.hasActivityOut).isFalse()
 
             wifiRepository.setWifiActivity(
-                DataActivityModel(
-                    hasActivityIn = false,
-                    hasActivityOut = true,
-                )
+                DataActivityModel(hasActivityIn = false, hasActivityOut = true)
             )
 
             assertThat(latest!!.hasActivityIn).isFalse()
@@ -178,10 +173,7 @@
             val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged.of(
-                    subscriptionId = SUB_ID + 10,
-                    level = 3,
-                )
+                WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID + 10, level = 3)
             )
 
             assertThat(latestLevel).isNotEqualTo(3)
@@ -199,10 +191,7 @@
             val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged.of(
-                    subscriptionId = SUB_ID,
-                    level = 3,
-                )
+                WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
             )
             wifiRepository.setIsWifiEnabled(false)
 
@@ -219,10 +208,7 @@
             val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged.of(
-                    subscriptionId = SUB_ID,
-                    level = 3,
-                )
+                WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
             )
             wifiRepository.setIsWifiDefault(false)
 
@@ -280,6 +266,7 @@
     fun networkName_usesSimOperatorNameAsInitial() =
         testScope.runTest {
             whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+            underTest = recreateRepo()
 
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -293,6 +280,10 @@
     fun networkName_updatesOnNetworkUpdate() =
         testScope.runTest {
             whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+            underTest = recreateRepo()
+
+            wifiRepository.setIsWifiEnabled(true)
+            wifiRepository.setIsWifiDefault(true)
 
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -301,10 +292,7 @@
 
             whenever(telephonyManager.simOperatorName).thenReturn("New SIM name")
             wifiRepository.setWifiNetwork(
-                WifiNetworkModel.CarrierMerged.of(
-                    subscriptionId = SUB_ID,
-                    level = 3,
-                )
+                WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
             )
 
             assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("New SIM name"))
@@ -320,7 +308,7 @@
             assertThat(latest).isTrue()
         }
 
-    private companion object {
+    companion object {
         const val SUB_ID = 123
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapterTest.kt
new file mode 100644
index 0000000..e72d0c2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapterTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2025 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.mobile.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.systemui.activated
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosAdapterTest.Companion.wrapRepo
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconInteractorKairosAdapterTest : MobileIconInteractorTestBase() {
+
+    var job: Job? = null
+    val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+    override fun createInteractor(overrides: MobileIconCarrierIdOverrides): MobileIconInteractor {
+        lateinit var result: MobileIconInteractor
+        job?.cancel()
+        job =
+            testScope.backgroundScope.launch {
+                kairosNetwork.activateSpec {
+                    val wrapped = wrap(mobileIconsInteractor)
+                    result =
+                        MobileIconInteractorKairosAdapter(
+                            kairosImpl =
+                                activated {
+                                    MobileIconInteractorKairosImpl(
+                                        defaultSubscriptionHasDataEnabled =
+                                            wrapped.activeDataConnectionHasDataEnabled,
+                                        alwaysShowDataRatIcon = wrapped.alwaysShowDataRatIcon,
+                                        alwaysUseCdmaLevel = wrapped.alwaysUseCdmaLevel,
+                                        isSingleCarrier = wrapped.isSingleCarrier,
+                                        mobileIsDefault = wrapped.mobileIsDefault,
+                                        defaultMobileIconMapping = wrapped.defaultMobileIconMapping,
+                                        defaultMobileIconGroup = wrapped.defaultMobileIconGroup,
+                                        isDefaultConnectionFailed =
+                                            wrapped.isDefaultConnectionFailed,
+                                        isForceHidden = wrapped.isForceHidden,
+                                        connectionRepository = wrapRepo(connectionRepository),
+                                        context = context,
+                                        carrierIdOverrides = overrides,
+                                    )
+                                }
+                        )
+                    Unit
+                }
+            }
+        testScope.runCurrent() // ensure the lateinit is set
+        return result
+    }
+
+    /** Allows us to wrap a (likely fake) MobileIconsInteractor into a Kairos version. */
+    private fun BuildScope.wrap(interactor: MobileIconsInteractor): MobileIconsInteractorKairos {
+        val filteredSubscriptions = interactor.filteredSubscriptions.toState(emptyList())
+        val icons = interactor.icons.toState()
+        return InteractorWrapper(
+            mobileIsDefault = interactor.mobileIsDefault.toState(),
+            filteredSubscriptions = filteredSubscriptions,
+            icons =
+                combine(filteredSubscriptions, icons) { subs, icons ->
+                        subs.zip(icons).associate { (subModel, icon) ->
+                            subModel.subscriptionId to buildSpec { wrap(icon) }
+                        }
+                    }
+                    .asIncremental()
+                    .applyLatestSpecForKey(),
+            isStackable = interactor.isStackable.toState(),
+            activeDataConnectionHasDataEnabled =
+                interactor.activeDataConnectionHasDataEnabled.toState(),
+            activeDataIconInteractor =
+                interactor.activeDataIconInteractor.toState().mapLatestBuild {
+                    it?.let { wrap(it) }
+                },
+            alwaysShowDataRatIcon = interactor.alwaysShowDataRatIcon.toState(),
+            alwaysUseCdmaLevel = interactor.alwaysUseCdmaLevel.toState(),
+            isSingleCarrier = interactor.isSingleCarrier.toState(),
+            defaultMobileIconMapping = interactor.defaultMobileIconMapping.toState(),
+            defaultMobileIconGroup = interactor.defaultMobileIconGroup.toState(),
+            isDefaultConnectionFailed = interactor.isDefaultConnectionFailed.toState(),
+            isUserSetUp = interactor.isUserSetUp.toState(),
+            isForceHidden = interactor.isForceHidden.toState(false),
+            isDeviceInEmergencyCallsOnlyMode =
+                interactor.isDeviceInEmergencyCallsOnlyMode.toState(false),
+        )
+    }
+
+    private fun BuildScope.wrap(interactor: MobileIconInteractor): MobileIconInteractorKairos =
+        // unused in tests
+        mock()
+
+    private class InteractorWrapper(
+        override val mobileIsDefault: State<Boolean>,
+        override val filteredSubscriptions: State<List<SubscriptionModel>>,
+        override val icons: Incremental<Int, MobileIconInteractorKairos>,
+        override val isStackable: State<Boolean>,
+        override val activeDataConnectionHasDataEnabled: State<Boolean>,
+        override val activeDataIconInteractor: State<MobileIconInteractorKairos?>,
+        override val alwaysShowDataRatIcon: State<Boolean>,
+        override val alwaysUseCdmaLevel: State<Boolean>,
+        override val isSingleCarrier: State<Boolean>,
+        override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>>,
+        override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup>,
+        override val isDefaultConnectionFailed: State<Boolean>,
+        override val isUserSetUp: State<Boolean>,
+        override val isForceHidden: State<Boolean>,
+        override val isDeviceInEmergencyCallsOnlyMode: State<Boolean>,
+    ) : MobileIconsInteractorKairos
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 8c70da7..974a475 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -22,6 +22,7 @@
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.telephony.flags.Flags
 import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
 import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
 import com.android.settingslib.mobile.TelephonyIcons
@@ -58,21 +59,40 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class MobileIconInteractorTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+class MobileIconInteractorTest : MobileIconInteractorTestBase() {
+    override fun createInteractor(overrides: MobileIconCarrierIdOverrides) =
+        MobileIconInteractorImpl(
+            testScope.backgroundScope,
+            mobileIconsInteractor.activeDataConnectionHasDataEnabled,
+            mobileIconsInteractor.alwaysShowDataRatIcon,
+            mobileIconsInteractor.alwaysUseCdmaLevel,
+            mobileIconsInteractor.isSingleCarrier,
+            mobileIconsInteractor.mobileIsDefault,
+            mobileIconsInteractor.defaultMobileIconMapping,
+            mobileIconsInteractor.defaultMobileIconGroup,
+            mobileIconsInteractor.isDefaultConnectionFailed,
+            mobileIconsInteractor.isForceHidden,
+            connectionRepository,
+            context,
+            overrides,
+        )
+}
 
-    private lateinit var underTest: MobileIconInteractor
-    private val mobileMappingsProxy = FakeMobileMappingsProxy()
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+abstract class MobileIconInteractorTestBase : SysuiTestCase() {
+    protected val kosmos = testKosmos()
 
-    private val connectionRepository =
+    protected lateinit var underTest: MobileIconInteractor
+    protected val mobileMappingsProxy = FakeMobileMappingsProxy()
+    protected val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+
+    protected val connectionRepository =
         FakeMobileConnectionRepository(
             SUB_1_ID,
             logcatTableLogBuffer(kosmos, "MobileIconInteractorTest"),
         )
 
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    protected val testDispatcher = UnconfinedTestDispatcher()
+    protected val testScope = TestScope(testDispatcher)
 
     @Before
     fun setUp() {
@@ -835,24 +855,9 @@
             assertThat(latest!!.level).isEqualTo(0)
         }
 
-    private fun createInteractor(
+    abstract fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
-    ) =
-        MobileIconInteractorImpl(
-            testScope.backgroundScope,
-            mobileIconsInteractor.activeDataConnectionHasDataEnabled,
-            mobileIconsInteractor.alwaysShowDataRatIcon,
-            mobileIconsInteractor.alwaysUseCdmaLevel,
-            mobileIconsInteractor.isSingleCarrier,
-            mobileIconsInteractor.mobileIsDefault,
-            mobileIconsInteractor.defaultMobileIconMapping,
-            mobileIconsInteractor.defaultMobileIconGroup,
-            mobileIconsInteractor.isDefaultConnectionFailed,
-            mobileIconsInteractor.isForceHidden,
-            connectionRepository,
-            context,
-            overrides,
-        )
+    ): MobileIconInteractor
 
     companion object {
         private const val GSM_LEVEL = 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapterTest.kt
new file mode 100644
index 0000000..787731e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapterTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2025 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.mobile.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.KairosBuilder
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.activateKairosActivatable
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.tableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconsInteractorKairosAdapterTest : MobileIconsInteractorTestBase() {
+    override fun Kosmos.createInteractor(): MobileIconsInteractor {
+        val userSetupRepo = FakeUserSetupRepository()
+        val repoK =
+            MobileConnectionsRepoWrapper(connectionsRepository).also {
+                activateKairosActivatable(it)
+            }
+        val kairosInteractor =
+            MobileIconsInteractorKairosImpl(
+                    mobileConnectionsRepo = repoK,
+                    carrierConfigTracker = carrierConfigTracker,
+                    tableLogger = mock(),
+                    connectivityRepository = connectivityRepository,
+                    userSetupRepo = userSetupRepo,
+                    context = context,
+                    featureFlagsClassic = featureFlagsClassic,
+                )
+                .also { activateKairosActivatable(it) }
+        return MobileIconsInteractorKairosAdapter(
+                kairosInteractor = kairosInteractor,
+                repo = connectionsRepository,
+                repoK = repoK,
+                kairosNetwork = kairos,
+                scope = applicationCoroutineScope,
+                context = context,
+                mobileMappingsProxy = mobileMappingsProxy,
+                userSetupRepo = userSetupRepo,
+                logFactory = tableLogBufferFactory,
+            )
+            .also {
+                activateKairosActivatable(it)
+                runCurrent()
+            }
+    }
+
+    /** Allows us to wrap a (likely fake) MobileConnectionsRepository into a Kairos version. */
+    private class MobileConnectionsRepoWrapper(val unwrapped: MobileConnectionsRepository) :
+        MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+        override val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos> =
+            buildIncremental {
+                unwrapped.subscriptions
+                    .toState()
+                    .map { it.associate { it.subscriptionId to Unit } }
+                    .asIncremental()
+                    .mapValues { (subId, _) ->
+                        buildSpec { wrapRepo(unwrapped.getRepoForSubId(subId)) }
+                    }
+                    .applyLatestSpecForKey()
+            }
+        override val subscriptions: State<Collection<SubscriptionModel>> = buildState {
+            unwrapped.subscriptions.toState()
+        }
+        override val activeMobileDataSubscriptionId: State<Int?> = buildState {
+            unwrapped.activeMobileDataSubscriptionId.toState()
+        }
+        override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+            buildState {
+                unwrapped.activeMobileDataRepository.toState().mapLatestBuild {
+                    it?.let { wrapRepo(it) }
+                }
+            }
+        override val activeSubChangedInGroupEvent: Events<Unit> = buildEvents {
+            unwrapped.activeSubChangedInGroupEvent.toEvents()
+        }
+        override val defaultDataSubId: State<Int?> = buildState {
+            unwrapped.defaultDataSubId.toState()
+        }
+        override val mobileIsDefault: State<Boolean> = buildState {
+            unwrapped.mobileIsDefault.toState()
+        }
+        override val hasCarrierMergedConnection: State<Boolean> = buildState {
+            unwrapped.hasCarrierMergedConnection.toState(false)
+        }
+        override val defaultConnectionIsValidated: State<Boolean> = buildState {
+            unwrapped.defaultConnectionIsValidated.toState()
+        }
+        override val defaultDataSubRatConfig: State<MobileMappings.Config> = buildState {
+            unwrapped.defaultDataSubRatConfig.toState()
+        }
+        override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>> =
+            buildState {
+                unwrapped.defaultMobileIconMapping.toState(emptyMap())
+            }
+        override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup> = buildState {
+            unwrapped.defaultMobileIconGroup.toState(TelephonyIcons.THREE_G)
+        }
+        override val isDeviceEmergencyCallCapable: State<Boolean> = buildState {
+            unwrapped.isDeviceEmergencyCallCapable.toState()
+        }
+        override val isAnySimSecure: State<Boolean> = buildState {
+            unwrapped.isDeviceEmergencyCallCapable.toState()
+        }
+        override val isInEcmMode: State<Boolean> = stateOf(false)
+    }
+
+    private class MobileConnectionRepoWrapper(
+        override val subId: Int,
+        override val carrierId: State<Int>,
+        override val inflateSignalStrength: State<Boolean>,
+        override val allowNetworkSliceIndicator: State<Boolean>,
+        override val tableLogBuffer: TableLogBuffer,
+        override val isEmergencyOnly: State<Boolean>,
+        override val isRoaming: State<Boolean>,
+        override val operatorAlphaShort: State<String?>,
+        override val isInService: State<Boolean>,
+        override val isNonTerrestrial: State<Boolean>,
+        override val isGsm: State<Boolean>,
+        override val cdmaLevel: State<Int>,
+        override val primaryLevel: State<Int>,
+        override val satelliteLevel: State<Int>,
+        override val dataConnectionState: State<DataConnectionState>,
+        override val dataActivityDirection: State<DataActivityModel>,
+        override val carrierNetworkChangeActive: State<Boolean>,
+        override val resolvedNetworkType: State<ResolvedNetworkType>,
+        override val numberOfLevels: State<Int>,
+        override val dataEnabled: State<Boolean>,
+        override val cdmaRoaming: State<Boolean>,
+        override val networkName: State<NetworkNameModel>,
+        override val carrierName: State<NetworkNameModel>,
+        override val isAllowedDuringAirplaneMode: State<Boolean>,
+        override val hasPrioritizedNetworkCapabilities: State<Boolean>,
+        override val isInEcmMode: State<Boolean>,
+    ) : MobileConnectionRepositoryKairos
+
+    companion object {
+        /** Allows us to wrap a (likely fake) MobileConnectionRepository into a Kairos version. */
+        fun BuildScope.wrapRepo(
+            conn: MobileConnectionRepository
+        ): MobileConnectionRepositoryKairos =
+            with(conn) {
+                MobileConnectionRepoWrapper(
+                    subId = subId,
+                    carrierId = carrierId.toState(),
+                    inflateSignalStrength = inflateSignalStrength.toState(),
+                    allowNetworkSliceIndicator = allowNetworkSliceIndicator.toState(),
+                    tableLogBuffer = tableLogBuffer,
+                    isEmergencyOnly = isEmergencyOnly.toState(),
+                    isRoaming = isRoaming.toState(),
+                    operatorAlphaShort = operatorAlphaShort.toState(),
+                    isInService = isInService.toState(),
+                    isNonTerrestrial = isNonTerrestrial.toState(),
+                    isGsm = isGsm.toState(),
+                    cdmaLevel = cdmaLevel.toState(),
+                    primaryLevel = primaryLevel.toState(),
+                    satelliteLevel = satelliteLevel.toState(),
+                    dataConnectionState = dataConnectionState.toState(),
+                    dataActivityDirection = dataActivityDirection.toState(),
+                    carrierNetworkChangeActive = carrierNetworkChangeActive.toState(),
+                    resolvedNetworkType = resolvedNetworkType.toState(),
+                    numberOfLevels = numberOfLevels.toState(),
+                    dataEnabled = dataEnabled.toState(),
+                    cdmaRoaming = cdmaRoaming.toState(),
+                    networkName = networkName.toState(),
+                    carrierName = carrierName.toState(),
+                    isAllowedDuringAirplaneMode = isAllowedDuringAirplaneMode.toState(),
+                    hasPrioritizedNetworkCapabilities = hasPrioritizedNetworkCapabilities.toState(),
+                    isInEcmMode = stateOf(false),
+                )
+            }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 9e914ad..356e567 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -58,8 +58,35 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class MobileIconsInteractorTest : SysuiTestCase() {
-    private val kosmos by lazy {
+class MobileIconsInteractorTest : MobileIconsInteractorTestBase() {
+    override fun Kosmos.createInteractor() =
+        MobileIconsInteractorImpl(
+            mobileConnectionsRepository,
+            carrierConfigTracker,
+            tableLogger = mock(),
+            connectivityRepository,
+            FakeUserSetupRepository(),
+            testScope.backgroundScope,
+            context,
+            featureFlagsClassic,
+        )
+
+    @Test
+    fun iconInteractor_cachedPerSubId() =
+        kosmos.runTest {
+            connectionsRepository.setSubscriptions(listOf(SUB_1))
+            runCurrent()
+
+            val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+            val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+
+            assertThat(interactor1).isNotNull()
+            assertThat(interactor1).isSameInstanceAs(interactor2)
+        }
+}
+
+abstract class MobileIconsInteractorTestBase : SysuiTestCase() {
+    protected val kosmos by lazy {
         testKosmos().apply {
             mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest"
             mobileConnectionsRepository.fake.run {
@@ -78,22 +105,13 @@
     }
 
     // shortcut rename
-    private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
+    protected val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
 
-    private val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
+    protected val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
 
-    private val Kosmos.underTest by Fixture {
-        MobileIconsInteractorImpl(
-            mobileConnectionsRepository,
-            carrierConfigTracker,
-            tableLogger = mock(),
-            connectivityRepository,
-            FakeUserSetupRepository(),
-            testScope.backgroundScope,
-            context,
-            featureFlagsClassic,
-        )
-    }
+    protected val Kosmos.underTest by Fixture { createInteractor() }
+
+    abstract fun Kosmos.createInteractor(): MobileIconsInteractor
 
     @Test
     fun filteredSubscriptions_default() =
@@ -744,12 +762,15 @@
             val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = true
+            runCurrent()
             assertThat(latest).isTrue()
 
             connectionsRepository.mobileIsDefault.value = false
+            runCurrent()
             assertThat(latest).isFalse()
 
             connectionsRepository.hasCarrierMergedConnection.value = true
+            runCurrent()
             assertThat(latest).isTrue()
         }
 
@@ -874,16 +895,6 @@
         }
 
     @Test
-    fun iconInteractor_cachedPerSubId() =
-        kosmos.runTest {
-            val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
-            val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
-
-            assertThat(interactor1).isNotNull()
-            assertThat(interactor1).isSameInstanceAs(interactor2)
-        }
-
-    @Test
     fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
@@ -1007,8 +1018,8 @@
 
     companion object {
 
-        private const val SUB_1_ID = 1
-        private val SUB_1 =
+        const val SUB_1_ID = 1
+        val SUB_1 =
             SubscriptionModel(
                 subscriptionId = SUB_1_ID,
                 carrierName = "Carrier $SUB_1_ID",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt
new file mode 100644
index 0000000..5695df5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 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.mobile.data.repository.prod
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.activated
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.mockito.Mockito
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionKairosAdapterTelephonySmokeTests : MobileConnectionTelephonySmokeTestsBase() {
+
+    var job: Job? = null
+    val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+    override fun recreateRepo(): MobileConnectionRepository {
+        lateinit var adapter: MobileConnectionRepositoryKairosAdapter
+        job?.cancel()
+        Mockito.clearInvocations(telephonyManager)
+        job =
+            testScope.backgroundScope.launch {
+                kairosNetwork.activateSpec {
+                    val repo = activated {
+                        MobileConnectionRepositoryKairosImpl(
+                            MobileConnectionRepositoryTest.SUB_1_ID,
+                            context,
+                            subscriptionModel.toState(),
+                            MobileConnectionRepositoryTest.DEFAULT_NAME_MODEL,
+                            MobileConnectionRepositoryTest.SEP,
+                            connectivityManager,
+                            telephonyManager,
+                            systemUiCarrierConfig,
+                            fakeBroadcastDispatcher,
+                            mobileMappings,
+                            testDispatcher,
+                            logger,
+                            tableLogger,
+                            flags,
+                        )
+                    }
+                    adapter = MobileConnectionRepositoryKairosAdapter(repo, systemUiCarrierConfig)
+                    Unit
+                }
+            }
+        testScope.runCurrent()
+        return adapter
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt
new file mode 100644
index 0000000..0cb7c1e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2025 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.mobile.data.repository.prod
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.activated
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileConnectionRepositoryKairosAdapterTest : MobileConnectionRepositoryTest() {
+
+    var job: Job? = null
+    val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+    override fun recreateRepo(): MobileConnectionRepository {
+        lateinit var adapter: MobileConnectionRepositoryKairosAdapter
+        job?.cancel()
+        Mockito.clearInvocations(telephonyManager)
+        job =
+            testScope.backgroundScope.launch {
+                kairosNetwork.activateSpec {
+                    val repo = activated {
+                        MobileConnectionRepositoryKairosImpl(
+                            SUB_1_ID,
+                            context,
+                            subscriptionModel.toState(),
+                            DEFAULT_NAME_MODEL,
+                            SEP,
+                            connectivityManager,
+                            telephonyManager,
+                            systemUiCarrierConfig,
+                            fakeBroadcastDispatcher,
+                            mobileMappings,
+                            testDispatcher,
+                            logger,
+                            tableLogger,
+                            flags,
+                        )
+                    }
+                    adapter = MobileConnectionRepositoryKairosAdapter(repo, systemUiCarrierConfig)
+                    Unit
+                }
+            }
+        testScope.runCurrent() // ensure the lateinit is set
+        return adapter
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index ed8be9b..2636195 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -88,6 +88,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
 import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
 import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfigWithOverride
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
@@ -116,25 +117,49 @@
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class MobileConnectionRepositoryTest : SysuiTestCase() {
-    private lateinit var underTest: MobileConnectionRepositoryImpl
+class MobileConnectionRepositoryImplTest : MobileConnectionRepositoryTest() {
+    override fun recreateRepo(): MobileConnectionRepository =
+        MobileConnectionRepositoryImpl(
+            SUB_1_ID,
+            context,
+            subscriptionModel,
+            DEFAULT_NAME_MODEL,
+            SEP,
+            connectivityManager,
+            telephonyManager,
+            systemUiCarrierConfig,
+            fakeBroadcastDispatcher,
+            mobileMappings,
+            testDispatcher,
+            logger,
+            tableLogger,
+            flags,
+            testScope.backgroundScope,
+        )
+}
 
-    private val flags =
+abstract class MobileConnectionRepositoryTest : SysuiTestCase() {
+
+    abstract fun recreateRepo(): MobileConnectionRepository
+
+    lateinit var underTest: MobileConnectionRepository
+
+    protected val flags =
         FakeFeatureFlagsClassic().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
 
-    @Mock private lateinit var connectivityManager: ConnectivityManager
-    @Mock private lateinit var telephonyManager: TelephonyManager
-    @Mock private lateinit var logger: MobileInputLogger
-    @Mock private lateinit var tableLogger: TableLogBuffer
-    @Mock private lateinit var context: Context
+    @Mock protected lateinit var connectivityManager: ConnectivityManager
+    @Mock protected lateinit var telephonyManager: TelephonyManager
+    @Mock protected lateinit var logger: MobileInputLogger
+    @Mock protected lateinit var tableLogger: TableLogBuffer
+    @Mock protected lateinit var context: Context
 
-    private val mobileMappings = FakeMobileMappingsProxy()
-    private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
+    protected val mobileMappings = FakeMobileMappingsProxy()
+    protected val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
 
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    protected val testDispatcher = UnconfinedTestDispatcher()
+    protected val testScope = TestScope(testDispatcher)
 
-    private val subscriptionModel: MutableStateFlow<SubscriptionModel?> =
+    protected val subscriptionModel: MutableStateFlow<SubscriptionModel?> =
         MutableStateFlow(
             SubscriptionModel(
                 subscriptionId = SUB_1_ID,
@@ -144,28 +169,11 @@
         )
 
     @Before
-    fun setUp() {
+    fun setUpBase() {
         MockitoAnnotations.initMocks(this)
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
-        underTest =
-            MobileConnectionRepositoryImpl(
-                SUB_1_ID,
-                context,
-                subscriptionModel,
-                DEFAULT_NAME_MODEL,
-                SEP,
-                connectivityManager,
-                telephonyManager,
-                systemUiCarrierConfig,
-                fakeBroadcastDispatcher,
-                mobileMappings,
-                testDispatcher,
-                logger,
-                tableLogger,
-                flags,
-                testScope.backgroundScope,
-            )
+        underTest = recreateRepo()
     }
 
     @Test
@@ -400,6 +408,7 @@
     fun carrierId_initialValueCaptured() =
         testScope.runTest {
             whenever(telephonyManager.simCarrierId).thenReturn(1234)
+            underTest = recreateRepo()
 
             var latest: Int? = null
             val job = underTest.carrierId.onEach { latest = it }.launchIn(this)
@@ -430,6 +439,8 @@
     @Test
     fun carrierNetworkChange() =
         testScope.runTest {
+            underTest = recreateRepo()
+
             var latest: Boolean? = null
             val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
 
@@ -622,24 +633,7 @@
             flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
 
             // Re-create the repository, because the flag is read at init
-            underTest =
-                MobileConnectionRepositoryImpl(
-                    SUB_1_ID,
-                    context,
-                    subscriptionModel,
-                    DEFAULT_NAME_MODEL,
-                    SEP,
-                    connectivityManager,
-                    telephonyManager,
-                    systemUiCarrierConfig,
-                    fakeBroadcastDispatcher,
-                    mobileMappings,
-                    testDispatcher,
-                    logger,
-                    tableLogger,
-                    flags,
-                    testScope.backgroundScope,
-                )
+            underTest = recreateRepo()
 
             var latest: Boolean? = null
             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
@@ -671,24 +665,7 @@
             flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, false)
 
             // Re-create the repository, because the flag is read at init
-            underTest =
-                MobileConnectionRepositoryImpl(
-                    SUB_1_ID,
-                    context,
-                    subscriptionModel,
-                    DEFAULT_NAME_MODEL,
-                    SEP,
-                    connectivityManager,
-                    telephonyManager,
-                    systemUiCarrierConfig,
-                    fakeBroadcastDispatcher,
-                    mobileMappings,
-                    testDispatcher,
-                    logger,
-                    tableLogger,
-                    flags,
-                    testScope.backgroundScope,
-                )
+            underTest = recreateRepo()
 
             var latest: Boolean? = null
             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
@@ -1441,14 +1418,14 @@
         }
 
     companion object {
-        private const val SUB_1_ID = 1
+        const val SUB_1_ID = 1
 
-        private const val DEFAULT_NAME = "Fake Mobile Network"
-        private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
-        private const val SEP = "-"
+        const val DEFAULT_NAME = "Fake Mobile Network"
+        val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+        const val SEP = "-"
 
-        private const val SPN = "testSpn"
-        private const val DATA_SPN = "testDataSpn"
-        private const val PLMN = "testPlmn"
+        const val SPN = "testSpn"
+        const val DATA_SPN = "testDataSpn"
+        const val PLMN = "testPlmn"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 6f21e79..caa6e21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
 import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -92,47 +93,53 @@
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @SmallTest
-class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
-    private lateinit var underTest: MobileConnectionRepositoryImpl
+class MobileConnectionTelephonySmokeTests : MobileConnectionTelephonySmokeTestsBase() {
+    override fun recreateRepo(): MobileConnectionRepository =
+        MobileConnectionRepositoryImpl(
+            SUB_1_ID,
+            context,
+            subscriptionModel,
+            DEFAULT_NAME,
+            SEP,
+            connectivityManager,
+            telephonyManager,
+            systemUiCarrierConfig,
+            fakeBroadcastDispatcher,
+            mobileMappings,
+            testDispatcher,
+            logger,
+            tableLogger,
+            flags,
+            testScope.backgroundScope,
+        )
+}
 
-    private val flags =
+abstract class MobileConnectionTelephonySmokeTestsBase : SysuiTestCase() {
+    protected lateinit var underTest: MobileConnectionRepository
+
+    protected val flags =
         FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
 
-    @Mock private lateinit var connectivityManager: ConnectivityManager
-    @Mock private lateinit var telephonyManager: TelephonyManager
-    @Mock private lateinit var logger: MobileInputLogger
-    @Mock private lateinit var tableLogger: TableLogBuffer
-    @Mock private lateinit var subscriptionModel: StateFlow<SubscriptionModel?>
+    @Mock protected lateinit var connectivityManager: ConnectivityManager
+    @Mock protected lateinit var telephonyManager: TelephonyManager
+    @Mock protected lateinit var logger: MobileInputLogger
+    @Mock protected lateinit var tableLogger: TableLogBuffer
+    @Mock protected lateinit var subscriptionModel: StateFlow<SubscriptionModel?>
 
-    private val mobileMappings = FakeMobileMappingsProxy()
-    private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
+    protected val mobileMappings = FakeMobileMappingsProxy()
+    protected val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
 
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    protected val testDispatcher = UnconfinedTestDispatcher()
+    protected val testScope = TestScope(testDispatcher)
+
+    abstract fun recreateRepo(): MobileConnectionRepository
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
-        underTest =
-            MobileConnectionRepositoryImpl(
-                SUB_1_ID,
-                context,
-                subscriptionModel,
-                DEFAULT_NAME,
-                SEP,
-                connectivityManager,
-                telephonyManager,
-                systemUiCarrierConfig,
-                fakeBroadcastDispatcher,
-                mobileMappings,
-                testDispatcher,
-                logger,
-                tableLogger,
-                flags,
-                testScope.backgroundScope,
-            )
+        underTest = recreateRepo()
     }
 
     @Test
@@ -329,9 +336,9 @@
     }
 
     companion object {
-        private const val SUB_1_ID = 1
+        const val SUB_1_ID = 1
 
-        private val DEFAULT_NAME = NetworkNameModel.Default("default name")
-        private const val SEP = "-"
+        val DEFAULT_NAME = NetworkNameModel.Default("default name")
+        const val SEP = "-"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt
new file mode 100644
index 0000000..65849ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2025 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.mobile.data.repository.prod
+
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeCarrierConfigRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairosAdapter
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
+// to run the callback and this makes the looper place nicely with TestScope etc.
+@TestableLooper.RunWithLooper
+class MobileConnectionsRepositoryKairosAdapterTest :
+    MobileConnectionsRepositoryTest<MobileConnectionsRepositoryKairosAdapter>() {
+
+    var job: Job? = null
+    val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+    override fun recreateRepo(): MobileConnectionsRepositoryKairosAdapter {
+        val carrierConfigRepo = FakeCarrierConfigRepository()
+        lateinit var connectionsRepo: MobileConnectionsRepositoryKairosImpl
+        connectionsRepo =
+            MobileConnectionsRepositoryKairosImpl(
+                connectivityRepository = connectivityRepository,
+                subscriptionManager = subscriptionManager,
+                subscriptionManagerProxy = subscriptionManagerProxy,
+                telephonyManager = telephonyManager,
+                logger = logger,
+                tableLogger = summaryLogger,
+                mobileMappingsProxy = mobileMappings,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                context = context,
+                bgDispatcher = testDispatcher,
+                mainDispatcher = testDispatcher,
+                airplaneModeRepository = airplaneModeRepository,
+                wifiRepository = wifiRepository,
+                keyguardUpdateMonitor = updateMonitor,
+                dumpManager = mock(),
+                mobileRepoFactory = {
+                    MobileConnectionRepositoryKairosFactoryImpl(
+                        context = context,
+                        connectionsRepo = connectionsRepo,
+                        logFactory = logBufferFactory,
+                        carrierConfigRepo = carrierConfigRepo,
+                        telephonyManager = telephonyManager,
+                        mobileRepoFactory = {
+                            subId,
+                            mobileLogger,
+                            subscriptionModel,
+                            defaultNetworkName,
+                            networkNameSeparator,
+                            systemUiCarrierConfig,
+                            telephonyManager ->
+                            MobileConnectionRepositoryKairosImpl(
+                                subId = subId,
+                                context = context,
+                                subscriptionModel = subscriptionModel,
+                                defaultNetworkName = defaultNetworkName,
+                                networkNameSeparator = networkNameSeparator,
+                                connectivityManager = connectivityManager,
+                                telephonyManager = telephonyManager,
+                                systemUiCarrierConfig = systemUiCarrierConfig,
+                                broadcastDispatcher = fakeBroadcastDispatcher,
+                                mobileMappingsProxy = mobileMappings,
+                                bgDispatcher = testDispatcher,
+                                logger = logger,
+                                tableLogBuffer = mobileLogger,
+                                flags = flags,
+                            )
+                        },
+                        mergedRepoFactory =
+                            CarrierMergedConnectionRepositoryKairos.Factory(
+                                telephonyManager,
+                                wifiRepository,
+                            ),
+                    )
+                },
+            )
+
+        val adapter =
+            MobileConnectionsRepositoryKairosAdapter(
+                kairosRepo = connectionsRepo,
+                kairosNetwork = kairosNetwork,
+                scope = testScope.backgroundScope,
+                connectivityRepository = connectivityRepository,
+                context = context,
+                carrierConfigRepo = carrierConfigRepo,
+            )
+
+        job?.cancel()
+        Mockito.clearInvocations(telephonyManager)
+        job =
+            testScope.backgroundScope.launch {
+                kairosNetwork.activateSpec {
+                    connectionsRepo.run { activate() }
+                    adapter.run { activate() }
+                }
+            }
+        testScope.runCurrent() // ensure everything is activated
+        return adapter
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index d1d6e27..c3662880 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,6 +37,7 @@
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.EmergencyCallbackModeListener
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -58,6 +59,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.carrierConfigRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -78,6 +80,7 @@
 import com.android.wifitrackerlib.WifiEntry
 import com.android.wifitrackerlib.WifiPickerTracker
 import com.google.common.truth.Truth.assertThat
+import java.time.Duration
 import java.util.UUID
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
@@ -93,6 +96,7 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
+import org.mockito.Mockito.atLeast
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.any
@@ -104,34 +108,311 @@
 // This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
 // to run the callback and this makes the looper place nicely with TestScope etc.
 @TestableLooper.RunWithLooper
-class MobileConnectionsRepositoryTest : SysuiTestCase() {
+class MobileConnectionsRepositoryImplTest :
+    MobileConnectionsRepositoryTest<MobileConnectionsRepositoryImpl>() {
+    override fun recreateRepo() =
+        MobileConnectionsRepositoryImpl(
+            connectivityRepository = connectivityRepository,
+            subscriptionManager = subscriptionManager,
+            subscriptionManagerProxy = subscriptionManagerProxy,
+            telephonyManager = telephonyManager,
+            logger = logger,
+            tableLogger = summaryLogger,
+            mobileMappingsProxy = mobileMappings,
+            broadcastDispatcher = fakeBroadcastDispatcher,
+            context = context,
+            bgDispatcher = testDispatcher,
+            scope = testScope.backgroundScope,
+            mainDispatcher = testDispatcher,
+            airplaneModeRepository = airplaneModeRepository,
+            wifiRepository = wifiRepository,
+            fullMobileRepoFactory = fullConnectionFactory,
+            keyguardUpdateMonitor = updateMonitor,
+            dumpManager = mock(),
+        )
+
+    @Test
+    fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
+        testScope.runTest {
+            val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+            collectLastValue(underTest.subscriptions)
+
+            // GIVEN active repo is updated before the subscription list updates
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(activeRepo).isNotNull()
+
+            // GIVEN the subscription list is then updated which includes the active data sub id
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // WHEN requesting a connection repository for the subscription
+            val newRepo = underTest.getRepoForSubId(SUB_2_ID)
+
+            // THEN the newly request repo has been cached and reused
+            assertThat(activeRepo).isSameInstanceAs(newRepo)
+        }
+
+    @Test
+    fun testConnectionRepository_invalidSubId_doesNotThrow() =
+        testScope.runTest {
+            underTest.getRepoForSubId(SUB_1_ID)
+            // No exception
+        }
+
+    @Test
+    fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+        }
+
+    @Test
+    fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+            // WHEN the wifi network updates to be not carrier merged
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            setWifiState(isCarrierMerged = false)
+            runCurrent()
+
+            // THEN the repos update
+            val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+        }
+
+    @Test
+    fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            setWifiState(isCarrierMerged = false)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+            runCurrent()
+
+            val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+            // WHEN the wifi network updates to be carrier merged
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
+            runCurrent()
+
+            // THEN the repos update
+            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+        }
+
+    @Test
+    @Ignore("b/333912012")
+    fun testConnectionCache_clearsInvalidSubscriptions() =
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger caching
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+            assertThat(underTest.getSubIdRepoCache())
+                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+            // SUB_2 disappears
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+        }
+
+    @Test
+    @Ignore("b/333912012")
+    fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            setWifiState(isCarrierMerged = true)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger caching
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+            val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID)
+
+            assertThat(underTest.getSubIdRepoCache())
+                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged)
+
+            // SUB_2 and SUB_CM disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+        }
+
+    /** Regression test for b/261706421 */
+    @Test
+    @Ignore("b/333912012")
+    fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger caching
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+            assertThat(underTest.getSubIdRepoCache())
+                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+            // All subscriptions disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(underTest.getSubIdRepoCache()).isEmpty()
+        }
+
+    @Test
+    fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
+        testScope.runTest {
+            var latestActiveRepo: MobileConnectionRepository? = null
+            collectLastValue(
+                underTest.activeMobileDataSubscriptionId.filterNotNull().onEach {
+                    latestActiveRepo = underTest.getRepoForSubId(it)
+                }
+            )
+
+            val latestSubscriptions by collectLastValue(underTest.subscriptions)
+
+            // Active data subscription id is sent, but no subscription change has been posted yet
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            // Subscriptions list is empty
+            assertThat(latestSubscriptions).isEmpty()
+            // getRepoForSubId does not throw
+            assertThat(latestActiveRepo).isNotNull()
+        }
+
+    @Test
+    fun testConnectionsCache_keepsReposCached() =
+        testScope.runTest {
+            // Collect subscriptions to start the job
+            collectLastValue(underTest.subscriptions)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
+
+            // All subscriptions disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Sub1 comes back
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
+
+            assertThat(repo1_1).isSameInstanceAs(repo1_2)
+        }
+
+    @Test
+    fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
+        testScope.runTest {
+            // Collect subscriptions to start the job
+            collectLastValue(underTest.subscriptions)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Client grabs a reference to a repository, but doesn't keep it around
+            underTest.getRepoForSubId(SUB_1_ID)
+
+            // All subscriptions disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+
+            assertThat(repo1).isNotNull()
+        }
+}
+
+abstract class MobileConnectionsRepositoryTest<T : MobileConnectionsRepository> : SysuiTestCase() {
     private val kosmos = testKosmos()
 
-    private val flags =
+    protected val flags =
         FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
 
     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
-    private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
-    private lateinit var connectivityRepository: ConnectivityRepository
-    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
-    private lateinit var wifiRepository: WifiRepository
+    protected lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
+    protected lateinit var connectivityRepository: ConnectivityRepository
+    protected lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    protected lateinit var wifiRepository: WifiRepository
     private lateinit var carrierConfigRepository: CarrierConfigRepository
 
-    @Mock private lateinit var connectivityManager: ConnectivityManager
-    @Mock private lateinit var subscriptionManager: SubscriptionManager
-    @Mock private lateinit var telephonyManager: TelephonyManager
-    @Mock private lateinit var logger: MobileInputLogger
-    private val summaryLogger = logcatTableLogBuffer(kosmos, "summaryLogger")
-    @Mock private lateinit var logBufferFactory: TableLogBufferFactory
-    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+    @Mock protected lateinit var connectivityManager: ConnectivityManager
+    @Mock protected lateinit var subscriptionManager: SubscriptionManager
+    @Mock protected lateinit var telephonyManager: TelephonyManager
+    @Mock protected lateinit var logger: MobileInputLogger
+    protected val summaryLogger = logcatTableLogBuffer(kosmos, "summaryLogger")
+    @Mock protected lateinit var logBufferFactory: TableLogBufferFactory
+    @Mock protected lateinit var updateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var wifiManager: WifiManager
     @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
     @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
     private val wifiTableLogBuffer = logcatTableLogBuffer(kosmos, "wifiTableLog")
 
-    private val mobileMappings = FakeMobileMappingsProxy()
-    private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+    protected val mobileMappings = FakeMobileMappingsProxy()
+    protected val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
     private val mainExecutor = FakeExecutor(FakeSystemClock())
     private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
     private val wifiPickerTrackerCallback =
@@ -139,10 +420,10 @@
     private val vcnTransportInfo = VcnTransportInfo.Builder().build()
     private val userRepository = kosmos.fakeUserRepository
 
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    protected val testDispatcher = StandardTestDispatcher()
+    protected val testScope = TestScope(testDispatcher)
 
-    private lateinit var underTest: MobileConnectionsRepositoryImpl
+    protected lateinit var underTest: T
 
     @Before
     fun setUp() {
@@ -237,30 +518,13 @@
                 carrierMergedRepoFactory = carrierMergedFactory,
             )
 
-        underTest =
-            MobileConnectionsRepositoryImpl(
-                connectivityRepository,
-                subscriptionManager,
-                subscriptionManagerProxy,
-                telephonyManager,
-                logger,
-                summaryLogger,
-                mobileMappings,
-                fakeBroadcastDispatcher,
-                context,
-                /* bgDispatcher = */ testDispatcher,
-                testScope.backgroundScope,
-                /* mainDispatcher = */ testDispatcher,
-                airplaneModeRepository,
-                wifiRepository,
-                fullConnectionFactory,
-                updateMonitor,
-                mock(),
-            )
+        underTest = recreateRepo()
 
         testScope.runCurrent()
     }
 
+    abstract fun recreateRepo(): T
+
     @Test
     fun testSubscriptions_initiallyEmpty() =
         testScope.runTest {
@@ -410,9 +674,17 @@
     fun activeRepo_updatesWithActiveDataId() =
         testScope.runTest {
             val latest by collectLastValue(underTest.activeMobileDataRepository)
+            runCurrent()
 
-            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
-                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_2))
+            getSubscriptionCallbacks().forEach { it.onSubscriptionsChanged() }
+            runCurrent()
+
+            getTelephonyCallbacksForType<ActiveDataSubscriptionIdListener>().forEach {
+                it.onActiveDataSubscriptionIdChanged(SUB_2_ID)
+            }
+            runCurrent()
 
             assertThat(latest?.subId).isEqualTo(SUB_2_ID)
         }
@@ -422,6 +694,10 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.activeMobileDataRepository)
 
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_2))
+            getSubscriptionCallbacks().forEach { it.onSubscriptionsChanged() }
+
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
@@ -437,60 +713,15 @@
     /** Regression test for b/268146648. */
     fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() =
         testScope.runTest {
-            val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+            val activeRepo = collectLastValue(underTest.activeMobileDataRepository)
             val subscriptions by collectLastValue(underTest.subscriptions)
 
-            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
-                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+            getTelephonyCallbacksForType<ActiveDataSubscriptionIdListener>().forEach {
+                it.onActiveDataSubscriptionIdChanged(SUB_2_ID)
+            }
 
             assertThat(subscriptions).isEmpty()
-            assertThat(activeRepo).isNotNull()
-        }
-
-    @Test
-    fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
-        testScope.runTest {
-            var latestActiveRepo: MobileConnectionRepository? = null
-            collectLastValue(
-                underTest.activeMobileDataSubscriptionId.filterNotNull().onEach {
-                    latestActiveRepo = underTest.getRepoForSubId(it)
-                }
-            )
-
-            val latestSubscriptions by collectLastValue(underTest.subscriptions)
-
-            // Active data subscription id is sent, but no subscription change has been posted yet
-            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
-                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
-            // Subscriptions list is empty
-            assertThat(latestSubscriptions).isEmpty()
-            // getRepoForSubId does not throw
-            assertThat(latestActiveRepo).isNotNull()
-        }
-
-    @Test
-    fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
-        testScope.runTest {
-            val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
-            collectLastValue(underTest.subscriptions)
-
-            // GIVEN active repo is updated before the subscription list updates
-            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
-                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
-            assertThat(activeRepo).isNotNull()
-
-            // GIVEN the subscription list is then updated which includes the active data sub id
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_2))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // WHEN requesting a connection repository for the subscription
-            val newRepo = underTest.getRepoForSubId(SUB_2_ID)
-
-            // THEN the newly request repo has been cached and reused
-            assertThat(activeRepo).isSameInstanceAs(newRepo)
+            activeRepo.invoke() // does not throw
         }
 
     @Test
@@ -501,6 +732,7 @@
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1))
             getSubscriptionCallback().onSubscriptionsChanged()
+            runCurrent()
 
             val repo1 = underTest.getRepoForSubId(SUB_1_ID)
             val repo2 = underTest.getRepoForSubId(SUB_1_ID)
@@ -525,80 +757,6 @@
             assertThat(repo1).isSameInstanceAs(repo2)
         }
 
-    @Test
-    fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
-        testScope.runTest {
-            collectLastValue(underTest.subscriptions)
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            setWifiState(isCarrierMerged = true)
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_CM))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
-            val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
-            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
-            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-        }
-
-    @Test
-    fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
-        testScope.runTest {
-            collectLastValue(underTest.subscriptions)
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            setWifiState(isCarrierMerged = true)
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_CM))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
-            var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
-            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
-            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            // WHEN the wifi network updates to be not carrier merged
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
-            setWifiState(isCarrierMerged = false)
-            runCurrent()
-
-            // THEN the repos update
-            val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
-            mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
-            assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
-            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-        }
-
-    @Test
-    fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
-        testScope.runTest {
-            collectLastValue(underTest.subscriptions)
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
-            setWifiState(isCarrierMerged = false)
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_CM))
-            getSubscriptionCallback().onSubscriptionsChanged()
-            runCurrent()
-
-            val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
-            var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
-            assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse()
-            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            // WHEN the wifi network updates to be carrier merged
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            setWifiState(isCarrierMerged = true)
-            runCurrent()
-
-            // THEN the repos update
-            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
-            mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
-            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
-            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-        }
-
     @SuppressLint("UnspecifiedRegisterReceiverFlag")
     @Test
     fun testDeviceEmergencyCallState_eagerlyChecksState() =
@@ -674,139 +832,6 @@
         }
 
     @Test
-    @Ignore("b/333912012")
-    fun testConnectionCache_clearsInvalidSubscriptions() =
-        testScope.runTest {
-            collectLastValue(underTest.subscriptions)
-
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_2))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // Get repos to trigger caching
-            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
-            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
-
-            assertThat(underTest.getSubIdRepoCache())
-                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
-
-            // SUB_2 disappears
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
-        }
-
-    @Test
-    @Ignore("b/333912012")
-    fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
-        testScope.runTest {
-            collectLastValue(underTest.subscriptions)
-
-            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
-            setWifiState(isCarrierMerged = true)
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // Get repos to trigger caching
-            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
-            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
-            val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID)
-
-            assertThat(underTest.getSubIdRepoCache())
-                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged)
-
-            // SUB_2 and SUB_CM disappear
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
-        }
-
-    /** Regression test for b/261706421 */
-    @Test
-    @Ignore("b/333912012")
-    fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
-        testScope.runTest {
-            collectLastValue(underTest.subscriptions)
-
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1, SUB_2))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // Get repos to trigger caching
-            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
-            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
-
-            assertThat(underTest.getSubIdRepoCache())
-                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
-
-            // All subscriptions disappear
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            assertThat(underTest.getSubIdRepoCache()).isEmpty()
-        }
-
-    @Test
-    fun testConnectionsCache_keepsReposCached() =
-        testScope.runTest {
-            // Collect subscriptions to start the job
-            collectLastValue(underTest.subscriptions)
-
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
-
-            // All subscriptions disappear
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // Sub1 comes back
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
-
-            assertThat(repo1_1).isSameInstanceAs(repo1_2)
-        }
-
-    @Test
-    fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
-        testScope.runTest {
-            // Collect subscriptions to start the job
-            collectLastValue(underTest.subscriptions)
-
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
-                .thenReturn(listOf(SUB_1))
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            // Client grabs a reference to a repository, but doesn't keep it around
-            underTest.getRepoForSubId(SUB_1_ID)
-
-            // All subscriptions disappear
-            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
-            getSubscriptionCallback().onSubscriptionsChanged()
-
-            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
-
-            assertThat(repo1).isNotNull()
-        }
-
-    @Test
-    fun testConnectionRepository_invalidSubId_doesNotThrow() =
-        testScope.runTest {
-            underTest.getRepoForSubId(SUB_1_ID)
-            // No exception
-        }
-
-    @Test
     fun connectionRepository_logBufferContainsSubIdInItsName() =
         testScope.runTest {
             collectLastValue(underTest.subscriptions)
@@ -814,6 +839,7 @@
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
             getSubscriptionCallback().onSubscriptionsChanged()
+            runCurrent()
 
             // Get repos to trigger creation
             underTest.getRepoForSubId(SUB_1_ID)
@@ -848,15 +874,27 @@
     fun defaultDataSubId_fetchesInitialValueOnStart() =
         testScope.runTest {
             subscriptionManagerProxy.defaultDataSubId = 2
+            underTest = recreateRepo()
+
             val latest by collectLastValue(underTest.defaultDataSubId)
 
             assertThat(latest).isEqualTo(2)
         }
 
+    private fun setDefaultDataSubId(subId: Int) {
+        subscriptionManagerProxy.defaultDataSubId = subId
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED).apply {
+                putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId)
+            },
+        )
+    }
+
     @Test
     fun defaultDataSubId_filtersOutInvalidSubIds() =
         testScope.runTest {
-            subscriptionManagerProxy.defaultDataSubId = INVALID_SUBSCRIPTION_ID
+            setDefaultDataSubId(INVALID_SUBSCRIPTION_ID)
             val latest by collectLastValue(underTest.defaultDataSubId)
 
             assertThat(latest).isNull()
@@ -865,15 +903,12 @@
     @Test
     fun defaultDataSubId_filtersOutInvalidSubIds_fromValidToInvalid() =
         testScope.runTest {
-            subscriptionManagerProxy.defaultDataSubId = 2
+            setDefaultDataSubId(2)
             val latest by collectLastValue(underTest.defaultDataSubId)
 
             assertThat(latest).isEqualTo(2)
 
-            val intent =
-                Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-                    .putExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+            setDefaultDataSubId(INVALID_SUBSCRIPTION_ID)
 
             assertThat(latest).isNull()
         }
@@ -881,7 +916,7 @@
     @Test
     fun defaultDataSubId_fetchesCurrentOnRestart() =
         testScope.runTest {
-            subscriptionManagerProxy.defaultDataSubId = 2
+            setDefaultDataSubId(2)
             var latest: Int? = null
             var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
             runCurrent()
@@ -894,7 +929,7 @@
 
             latest = null
 
-            subscriptionManagerProxy.defaultDataSubId = 1
+            setDefaultDataSubId(1)
 
             job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
             runCurrent()
@@ -1281,26 +1316,7 @@
 
             // The initial value will be fetched when the repo is created, so we need to override
             // the resources and then re-create the repo.
-            underTest =
-                MobileConnectionsRepositoryImpl(
-                    connectivityRepository,
-                    subscriptionManager,
-                    subscriptionManagerProxy,
-                    telephonyManager,
-                    logger,
-                    summaryLogger,
-                    mobileMappings,
-                    fakeBroadcastDispatcher,
-                    context,
-                    testDispatcher,
-                    testScope.backgroundScope,
-                    testDispatcher,
-                    airplaneModeRepository,
-                    wifiRepository,
-                    fullConnectionFactory,
-                    updateMonitor,
-                    mock(),
-                )
+            underTest = recreateRepo()
 
             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
 
@@ -1428,6 +1444,12 @@
             assertThat(underTest.getIsAnySimSecure()).isFalse()
 
             whenever(updateMonitor.isSimPinSecure).thenReturn(true)
+            org.mockito.kotlin
+                .argumentCaptor<KeyguardUpdateMonitorCallback>()
+                .apply { verify(updateMonitor, atLeast(0)).registerCallback(capture()) }
+                .allValues
+                .forEach { it.onSimStateChanged(0, 0, 0) }
+            runCurrent()
 
             assertThat(underTest.getIsAnySimSecure()).isTrue()
         }
@@ -1447,19 +1469,26 @@
         testScope.runTest {
             whenever(telephonyManager.emergencyCallbackMode).thenReturn(true)
 
+            getTelephonyCallbacksForType<EmergencyCallbackModeListener>().forEach {
+                it.onCallbackModeStarted(
+                    TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS,
+                    Duration.ZERO,
+                    0,
+                )
+            }
             runCurrent()
 
             assertThat(underTest.isInEcmMode()).isTrue()
         }
 
-    private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+    protected fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
         runCurrent()
         val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
         verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
         return callbackCaptor.value!!
     }
 
-    private fun setWifiState(isCarrierMerged: Boolean) {
+    protected fun setWifiState(isCarrierMerged: Boolean) {
         if (isCarrierMerged) {
             val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
@@ -1481,7 +1510,7 @@
         wifiPickerTrackerCallback.value.onWifiEntriesChanged()
     }
 
-    private fun TestScope.getSubscriptionCallback():
+    protected fun TestScope.getSubscriptionCallback():
         SubscriptionManager.OnSubscriptionsChangedListener {
         runCurrent()
         val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
@@ -1490,25 +1519,39 @@
         return callbackCaptor.value!!
     }
 
-    private fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> {
+    protected fun TestScope.getSubscriptionCallbacks():
+        List<SubscriptionManager.OnSubscriptionsChangedListener> {
         runCurrent()
-        val callbackCaptor = argumentCaptor<TelephonyCallback>()
-        verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+        val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+        verify(subscriptionManager, atLeast(0))
+            .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
         return callbackCaptor.allValues
     }
 
-    private inline fun <reified T> TestScope.getTelephonyCallbackForType(): T {
-        val cbs = this.getTelephonyCallbacks().filterIsInstance<T>()
+    fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> {
+        runCurrent()
+        val callbackCaptor = argumentCaptor<TelephonyCallback>()
+        verify(telephonyManager, atLeast(0))
+            .registerTelephonyCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.allValues
+    }
+
+    inline fun <reified T> TestScope.getTelephonyCallbackForType(): T {
+        val cbs = getTelephonyCallbacksForType<T>()
         assertThat(cbs.size).isEqualTo(1)
         return cbs[0]
     }
 
+    inline fun <reified T> TestScope.getTelephonyCallbacksForType(): List<T> {
+        return getTelephonyCallbacks().filterIsInstance<T>()
+    }
+
     companion object {
         // Subscription 1
-        private const val SUB_1_ID = 1
+        const val SUB_1_ID = 1
         private const val SUB_1_NAME = "Carrier $SUB_1_ID"
         private val GROUP_1 = ParcelUuid(UUID.randomUUID())
-        private val SUB_1 =
+        val SUB_1 =
             mock<SubscriptionInfo>().also {
                 whenever(it.subscriptionId).thenReturn(SUB_1_ID)
                 whenever(it.groupUuid).thenReturn(GROUP_1)
@@ -1524,10 +1567,10 @@
             )
 
         // Subscription 2
-        private const val SUB_2_ID = 2
+        const val SUB_2_ID = 2
         private const val SUB_2_NAME = "Carrier $SUB_2_ID"
         private val GROUP_2 = ParcelUuid(UUID.randomUUID())
-        private val SUB_2 =
+        val SUB_2 =
             mock<SubscriptionInfo>().also {
                 whenever(it.subscriptionId).thenReturn(SUB_2_ID)
                 whenever(it.groupUuid).thenReturn(GROUP_2)
@@ -1552,6 +1595,7 @@
                 whenever(it.subscriptionId).thenReturn(SUB_3_ID_GROUPED)
                 whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
                 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+                whenever(it.carrierName).thenReturn("")
             }
 
         // Subscription 4
@@ -1561,17 +1605,18 @@
                 whenever(it.subscriptionId).thenReturn(SUB_4_ID_GROUPED)
                 whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
                 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+                whenever(it.carrierName).thenReturn("")
             }
 
         // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
         private const val NET_ID = 123
-        private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
+        val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
 
         // Carrier merged subscription
-        private const val SUB_CM_ID = 5
+        const val SUB_CM_ID = 5
         private const val SUB_CM_NAME = "Carrier $SUB_CM_ID"
-        private val SUB_CM =
+        val SUB_CM =
             mock<SubscriptionInfo>().also {
                 whenever(it.subscriptionId).thenReturn(SUB_CM_ID)
                 whenever(it.carrierName).thenReturn(SUB_CM_NAME)
@@ -1590,7 +1635,7 @@
                 whenever(this.isCarrierMerged).thenReturn(true)
                 whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
             }
-        private val WIFI_NETWORK_CAPS_CM =
+        val WIFI_NETWORK_CAPS_CM =
             mock<NetworkCapabilities>().also {
                 whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                 whenever(it.transportInfo).thenReturn(WIFI_INFO_CM)
@@ -1602,7 +1647,7 @@
                 whenever(this.isPrimary).thenReturn(true)
                 whenever(this.isCarrierMerged).thenReturn(false)
             }
-        private val WIFI_NETWORK_CAPS_ACTIVE =
+        val WIFI_NETWORK_CAPS_ACTIVE =
             mock<NetworkCapabilities>().also {
                 whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                 whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE)