diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 8ac12379e59e5..22aca0a8b0d7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -39,7 +39,11 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,15 +64,19 @@ import kotlinx.coroutines.launch class DemoMobileConnectionsRepository @Inject constructor( - private val dataSource: DemoModeMobileConnectionDataSource, + private val mobileDataSource: DemoModeMobileConnectionDataSource, + private val wifiDataSource: DemoModeWifiDataSource, @Application private val scope: CoroutineScope, context: Context, private val logFactory: TableLogBufferFactory, ) : MobileConnectionsRepository { - private var demoCommandJob: Job? = null + private var mobileDemoCommandJob: Job? = null + private var wifiDemoCommandJob: Job? = null - private var connectionRepoCache = mutableMapOf() + private var carrierMergedSubId: Int? = null + + private var connectionRepoCache = mutableMapOf() private val subscriptionInfoCache = mutableMapOf() val demoModeFinishedEvent = MutableSharedFlow(extraBufferCapacity = 1) @@ -144,52 +152,83 @@ constructor( override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel()) override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository { - return connectionRepoCache[subId] - ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it } + val current = connectionRepoCache[subId]?.repo + if (current != null) { + return current + } + + val new = createDemoMobileConnectionRepo(subId) + connectionRepoCache[subId] = new + return new.repo } - private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository { - val tableLogBuffer = logFactory.getOrCreate("DemoMobileConnectionLog [$subId]", 100) + private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer { + val tableLogBuffer = + logFactory.getOrCreate( + "DemoMobileConnectionLog [$subId]", + MOBILE_CONNECTION_BUFFER_SIZE, + ) - return DemoMobileConnectionRepository( - subId, - tableLogBuffer, - ) + val repo = + DemoMobileConnectionRepository( + subId, + tableLogBuffer, + ) + return CacheContainer(repo, lastMobileState = null) } override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) fun startProcessingCommands() { - demoCommandJob = + mobileDemoCommandJob = scope.launch { - dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) } + mobileDataSource.mobileEvents.filterNotNull().collect { event -> + processMobileEvent(event) + } + } + wifiDemoCommandJob = + scope.launch { + wifiDataSource.wifiEvents.filterNotNull().collect { event -> + processWifiEvent(event) + } } } fun stopProcessingCommands() { - demoCommandJob?.cancel() + mobileDemoCommandJob?.cancel() + wifiDemoCommandJob?.cancel() _subscriptions.value = listOf() connectionRepoCache.clear() subscriptionInfoCache.clear() } - private fun processEvent(event: FakeNetworkEventModel) { + private fun processMobileEvent(event: FakeNetworkEventModel) { when (event) { is Mobile -> { processEnabledMobileState(event) } is MobileDisabled -> { - processDisabledMobileState(event) + maybeRemoveSubscription(event.subId) } } } + private fun processWifiEvent(event: FakeWifiEventModel) { + when (event) { + is FakeWifiEventModel.WifiDisabled -> disableCarrierMerged() + is FakeWifiEventModel.Wifi -> disableCarrierMerged() + is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event) + } + } + private fun processEnabledMobileState(state: Mobile) { // get or create the connection repo, and set its values val subId = state.subId ?: DEFAULT_SUB_ID maybeCreateSubscription(subId) val connection = getRepoForSubId(subId) + connectionRepoCache[subId]?.lastMobileState = state + // This is always true here, because we split out disabled states at the data-source level connection.dataEnabled.value = true connection.networkName.value = NetworkNameModel.Derived(state.name) @@ -198,14 +237,36 @@ constructor( connection.connectionInfo.value = state.toMobileConnectionModel() } - private fun processDisabledMobileState(state: MobileDisabled) { + private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) { + // The new carrier merged connection is for a different sub ID, so disable carrier merged + // for the current (now old) sub + if (carrierMergedSubId != event.subscriptionId) { + disableCarrierMerged() + } + + // get or create the connection repo, and set its values + val subId = event.subscriptionId + maybeCreateSubscription(subId) + carrierMergedSubId = subId + + val connection = getRepoForSubId(subId) + // This is always true here, because we split out disabled states at the data-source level + connection.dataEnabled.value = true + connection.networkName.value = NetworkNameModel.Derived(CARRIER_MERGED_NAME) + connection.numberOfLevels.value = event.numberOfLevels + connection.cdmaRoaming.value = false + connection.connectionInfo.value = event.toMobileConnectionModel() + Log.e("CCS", "output connection info = ${connection.connectionInfo.value}") + } + + private fun maybeRemoveSubscription(subId: Int?) { if (_subscriptions.value.isEmpty()) { // Nothing to do here return } - val subId = - state.subId + val finalSubId = + subId ?: run { // For sake of usability, we can allow for no subId arg if there is only one // subscription @@ -223,7 +284,21 @@ constructor( _subscriptions.value[0].subscriptionId } - removeSubscription(subId) + removeSubscription(finalSubId) + } + + private fun disableCarrierMerged() { + val currentCarrierMergedSubId = carrierMergedSubId ?: return + + // If this sub ID was previously not carrier merged, we should reset it to its previous + // connection. + val lastMobileState = connectionRepoCache[carrierMergedSubId]?.lastMobileState + if (lastMobileState != null) { + processEnabledMobileState(lastMobileState) + } else { + // Otherwise, just remove the subscription entirely + removeSubscription(currentCarrierMergedSubId) + } } private fun removeSubscription(subId: Int) { @@ -251,6 +326,10 @@ constructor( ) } + private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel { + return createCarrierMergedConnectionModel(this.level) + } + private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType { val key = mobileMappingsReverseLookup.value[this] ?: "dis" return DefaultNetworkType(key) @@ -260,9 +339,17 @@ constructor( private const val TAG = "DemoMobileConnectionsRepo" private const val DEFAULT_SUB_ID = 1 + + private const val CARRIER_MERGED_NAME = "Carrier Merged Network" } } +class CacheContainer( + var repo: DemoMobileConnectionRepository, + /** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */ + var lastMobileState: Mobile?, +) + class DemoMobileConnectionRepository( override val subId: Int, override val tableLogBuffer: TableLogBuffer, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt index c588945fbd677..caac8fa2f2c3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK import com.android.systemui.demomode.DemoModeController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -43,10 +44,10 @@ constructor( private fun Bundle.toWifiEvent(): FakeWifiEventModel? { val wifi = getString("wifi") ?: return null - return if (wifi == "show") { - activeWifiEvent() - } else { - FakeWifiEventModel.WifiDisabled + return when (wifi) { + "show" -> activeWifiEvent() + "carriermerged" -> carrierMergedWifiEvent() + else -> FakeWifiEventModel.WifiDisabled } } @@ -64,6 +65,14 @@ constructor( ) } + private fun Bundle.carrierMergedWifiEvent(): FakeWifiEventModel.CarrierMerged { + val subId = getString("slot")?.toInt() ?: DEFAULT_CARRIER_MERGED_SUB_ID + val level = getString("level")?.toInt() ?: 0 + val numberOfLevels = getString("numlevels")?.toInt() ?: DEFAULT_NUM_LEVELS + + return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels) + } + private fun String.toActivity(): Int = when (this) { "inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT @@ -71,4 +80,8 @@ constructor( "out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE } + + companion object { + const val DEFAULT_CARRIER_MERGED_SUB_ID = 10 + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index b57102496fa72..e161b3e42d02c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -66,6 +66,7 @@ constructor( private fun processEvent(event: FakeWifiEventModel) = when (event) { is FakeWifiEventModel.Wifi -> processEnabledWifiState(event) + is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event) is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState() } @@ -85,6 +86,14 @@ constructor( _wifiNetwork.value = event.toWifiNetworkModel() } + private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) { + _isWifiEnabled.value = true + _isWifiDefault.value = true + // TODO(b/238425913): Support activity in demo mode. + _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false) + _wifiNetwork.value = event.toCarrierMergedModel() + } + private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel = WifiNetworkModel.Active( networkId = DEMO_NET_ID, @@ -99,7 +108,13 @@ constructor( passpointProviderFriendlyName = null, ) - // TODO(b/238425913): Add carrier merged support. + private fun FakeWifiEventModel.CarrierMerged.toCarrierMergedModel(): WifiNetworkModel = + WifiNetworkModel.CarrierMerged( + networkId = DEMO_NET_ID, + subscriptionId = subscriptionId, + level = level, + numberOfLevels = numberOfLevels, + ) companion object { private const val DEMO_NET_ID = 1234 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt index 2353fb82f3b1b..518f8ce66d2e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt @@ -29,5 +29,11 @@ sealed interface FakeWifiEventModel { val validated: Boolean?, ) : FakeWifiEventModel + data class CarrierMerged( + val subscriptionId: Int, + val level: Int, + val numberOfLevels: Int, + ) : FakeWifiEventModel + object WifiDisabled : FakeWifiEventModel } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 5432a10b40637..0859d140c3b4f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.Mobil import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.mock @@ -72,7 +73,8 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { private lateinit var underTest: MobileRepositorySwitcher private lateinit var realRepo: MobileConnectionsRepositoryImpl private lateinit var demoRepo: DemoMobileConnectionsRepository - private lateinit var mockDataSource: DemoModeMobileConnectionDataSource + private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource + private lateinit var wifiDataSource: DemoModeWifiDataSource private lateinit var logFactory: TableLogBufferFactory private lateinit var wifiRepository: FakeWifiRepository @@ -98,10 +100,14 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { // Never start in demo mode whenever(demoModeController.isInDemoMode).thenReturn(false) - mockDataSource = + mobileDataSource = mock().also { whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow) } + wifiDataSource = + mock().also { + whenever(it.wifiEvents).thenReturn(MutableStateFlow(null)) + } wifiRepository = FakeWifiRepository() realRepo = @@ -122,7 +128,8 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { demoRepo = DemoMobileConnectionsRepository( - dataSource = mockDataSource, + mobileDataSource = mobileDataSource, + wifiDataSource = wifiDataSource, scope = scope, context = context, logFactory = logFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index 210208532dd45..6989b514a703c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -29,6 +29,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectio import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -63,10 +65,12 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC private val testScope = TestScope(testDispatcher) private val fakeNetworkEventFlow = MutableStateFlow(null) + private val fakeWifiEventFlow = MutableStateFlow(null) private lateinit var connectionsRepo: DemoMobileConnectionsRepository private lateinit var underTest: DemoMobileConnectionRepository private lateinit var mockDataSource: DemoModeMobileConnectionDataSource + private lateinit var mockWifiDataSource: DemoModeWifiDataSource @Before fun setUp() { @@ -75,10 +79,15 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC mock().also { whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow) } + mockWifiDataSource = + mock().also { + whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow) + } connectionsRepo = DemoMobileConnectionsRepository( - dataSource = mockDataSource, + mobileDataSource = mockDataSource, + wifiDataSource = mockWifiDataSource, scope = testScope.backgroundScope, context = context, logFactory = logFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index cdbe75e855bcb..9d16b7fe52460 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -32,6 +32,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionMod import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource +import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -57,21 +59,28 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { private val testScope = TestScope(testDispatcher) private val fakeNetworkEventFlow = MutableStateFlow(null) + private val fakeWifiEventFlow = MutableStateFlow(null) private lateinit var underTest: DemoMobileConnectionsRepository - private lateinit var mockDataSource: DemoModeMobileConnectionDataSource + private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource + private lateinit var wifiDataSource: DemoModeWifiDataSource @Before fun setUp() { // The data source only provides one API, so we can mock it with a flow here for convenience - mockDataSource = + mobileDataSource = mock().also { whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow) } + wifiDataSource = + mock().also { + whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow) + } underTest = DemoMobileConnectionsRepository( - dataSource = mockDataSource, + mobileDataSource = mobileDataSource, + wifiDataSource = wifiDataSource, scope = testScope.backgroundScope, context = context, logFactory = logFactory, @@ -96,6 +105,22 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `wifi carrier merged event - create new subscription`() = + testScope.runTest { + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEmpty() + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5) + + assertThat(latest).hasSize(1) + assertThat(latest!![0].subscriptionId).isEqualTo(5) + + job.cancel() + } + @Test fun `network event - reuses subscription when same Id`() = testScope.runTest { @@ -118,6 +143,28 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `wifi carrier merged event - reuses subscription when same Id`() = + testScope.runTest { + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEmpty() + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 1) + + assertThat(latest).hasSize(1) + assertThat(latest!![0].subscriptionId).isEqualTo(5) + + // Second network event comes in with the same subId, does not create a new subscription + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 2) + + assertThat(latest).hasSize(1) + assertThat(latest!![0].subscriptionId).isEqualTo(5) + + job.cancel() + } + @Test fun `multiple subscriptions`() = testScope.runTest { @@ -132,6 +179,35 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `mobile subscription and carrier merged subscription`() = + testScope.runTest { + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeNetworkEventFlow.value = validMobileEvent(subId = 1) + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5) + + assertThat(latest).hasSize(2) + + job.cancel() + } + + @Test + fun `multiple mobile subscriptions and carrier merged subscription`() = + testScope.runTest { + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeNetworkEventFlow.value = validMobileEvent(subId = 1) + fakeNetworkEventFlow.value = validMobileEvent(subId = 2) + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 3) + + assertThat(latest).hasSize(3) + + job.cancel() + } + @Test fun `mobile disabled event - disables connection - subId specified - single conn`() = testScope.runTest { @@ -194,6 +270,112 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `wifi network updates to disabled - carrier merged connection removed`() = + testScope.runTest { + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1) + + assertThat(latest).hasSize(1) + + fakeWifiEventFlow.value = FakeWifiEventModel.WifiDisabled + + assertThat(latest).isEmpty() + + job.cancel() + } + + @Test + fun `wifi network updates to active - carrier merged connection removed`() = + testScope.runTest { + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1) + + assertThat(latest).hasSize(1) + + fakeWifiEventFlow.value = + FakeWifiEventModel.Wifi( + level = 1, + activity = 0, + ssid = null, + validated = true, + ) + + assertThat(latest).isEmpty() + + job.cancel() + } + + @Test + fun `mobile sub updates to carrier merged - only one connection`() = + testScope.runTest { + var latestSubsList: List? = null + var connections: List? = null + val job = + underTest.subscriptions + .onEach { latestSubsList = it } + .onEach { infos -> + connections = + infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } + } + .launchIn(this) + + fakeNetworkEventFlow.value = validMobileEvent(subId = 3, level = 2) + assertThat(latestSubsList).hasSize(1) + + val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1) + fakeWifiEventFlow.value = carrierMergedEvent + assertThat(latestSubsList).hasSize(1) + val connection = connections!!.find { it.subId == 3 }!! + assertCarrierMergedConnection(connection, carrierMergedEvent) + + job.cancel() + } + + @Test + fun `mobile sub updates to carrier merged then back - has old mobile data`() = + testScope.runTest { + var latestSubsList: List? = null + var connections: List? = null + val job = + underTest.subscriptions + .onEach { latestSubsList = it } + .onEach { infos -> + connections = + infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } + } + .launchIn(this) + + val mobileEvent = validMobileEvent(subId = 3, level = 2) + fakeNetworkEventFlow.value = mobileEvent + assertThat(latestSubsList).hasSize(1) + + val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1) + fakeWifiEventFlow.value = carrierMergedEvent + assertThat(latestSubsList).hasSize(1) + var connection = connections!!.find { it.subId == 3 }!! + assertCarrierMergedConnection(connection, carrierMergedEvent) + + // WHEN the carrier merged is removed + fakeWifiEventFlow.value = + FakeWifiEventModel.Wifi( + level = 4, + activity = 0, + ssid = null, + validated = true, + ) + + // THEN the subId=3 connection goes back to the mobile information + connection = connections!!.find { it.subId == 3 }!! + assertConnection(connection, mobileEvent) + + job.cancel() + } + /** Regression test for b/261706421 */ @Test fun `multiple connections - remove all - does not throw`() = @@ -289,6 +471,51 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `demo connection - two connections - update carrier merged - no affect on first`() = + testScope.runTest { + var currentEvent1 = validMobileEvent(subId = 1) + var connection1: DemoMobileConnectionRepository? = null + var currentEvent2 = validCarrierMergedEvent(subId = 2) + var connection2: DemoMobileConnectionRepository? = null + var connections: List? = null + val job = + underTest.subscriptions + .onEach { infos -> + connections = + infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } + } + .launchIn(this) + + fakeNetworkEventFlow.value = currentEvent1 + fakeWifiEventFlow.value = currentEvent2 + assertThat(connections).hasSize(2) + connections!!.forEach { + when (it.subId) { + 1 -> connection1 = it + 2 -> connection2 = it + else -> Assert.fail("Unexpected subscription") + } + } + + assertConnection(connection1!!, currentEvent1) + assertCarrierMergedConnection(connection2!!, currentEvent2) + + // WHEN the event changes for connection 2, it updates, and connection 1 stays the same + currentEvent2 = validCarrierMergedEvent(subId = 2, level = 4) + fakeWifiEventFlow.value = currentEvent2 + assertConnection(connection1!!, currentEvent1) + assertCarrierMergedConnection(connection2!!, currentEvent2) + + // and vice versa + currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true) + fakeNetworkEventFlow.value = currentEvent1 + assertConnection(connection1!!, currentEvent1) + assertCarrierMergedConnection(connection2!!, currentEvent2) + + job.cancel() + } + private fun assertConnection( conn: DemoMobileConnectionRepository, model: FakeNetworkEventModel @@ -315,6 +542,21 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { else -> {} } } + + private fun assertCarrierMergedConnection( + conn: DemoMobileConnectionRepository, + model: FakeWifiEventModel.CarrierMerged, + ) { + val connectionInfo: MobileConnectionModel = conn.connectionInfo.value + assertThat(conn.subId).isEqualTo(model.subscriptionId) + assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level) + assertThat(connectionInfo.primaryLevel).isEqualTo(model.level) + assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false) + assertThat(connectionInfo.isRoaming).isEqualTo(false) + assertThat(connectionInfo.isEmergencyOnly).isFalse() + assertThat(connectionInfo.isGsm).isFalse() + assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected) + } } /** Convenience to create a valid fake network event with minimal params */ @@ -339,3 +581,14 @@ fun validMobileEvent( roaming = roaming, name = "demo name", ) + +fun validCarrierMergedEvent( + subId: Int = 1, + level: Int = 1, + numberOfLevels: Int = 4, +): FakeWifiEventModel.CarrierMerged = + FakeWifiEventModel.CarrierMerged( + subscriptionId = subId, + level = level, + numberOfLevels = numberOfLevels, + )