summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt223
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt1444
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt525
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt89
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt371
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt204
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/composable/kairos/HydratedComposeStateOf.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosKosmos.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt7
18 files changed, 1525 insertions, 1627 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt
index 57e63a595b8f..9042ac45cd4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -19,110 +19,69 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairos
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairos
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractorKairos
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
import com.android.systemui.testKosmos
-import com.android.systemui.util.CarrierConfigTracker
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalKairosApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class LocationBasedMobileIconViewModelKairosTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private lateinit var commonImpl: MobileIconViewModelCommonKairos
- private lateinit var homeIcon: HomeMobileIconViewModelKairos
- private lateinit var qsIcon: QsMobileIconViewModelKairos
- private lateinit var keyguardIcon: KeyguardMobileIconViewModelKairos
- private lateinit var iconsInteractor: MobileIconsInteractor
- private lateinit var interactor: MobileIconInteractor
- private val connectionsRepository = kosmos.fakeMobileConnectionsRepository
- private lateinit var repository: FakeMobileConnectionRepository
- private lateinit var airplaneModeInteractor: AirplaneModeInteractor
-
- private val connectivityRepository = FakeConnectivityRepository()
- private val flags =
- FakeFeatureFlagsClassic().also {
- it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
- }
- @Mock private lateinit var constants: ConnectivityConstants
- private val tableLogBuffer =
- logcatTableLogBuffer(kosmos, "LocationBasedMobileIconViewModelTest")
- @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
-
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- connectionsRepository,
- )
- repository =
- FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
- isInService.value = true
- cdmaLevel.value = 1
- primaryLevel.value = 1
- isEmergencyOnly.value = false
- numberOfLevels.value = 4
- resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(lookupKey = "3G")
- dataConnectionState.value = DataConnectionState.Connected
- }
+ private val Kosmos.commonImpl: MobileIconViewModelKairosCommon by ActivatedKairosFixture {
+ MobileIconViewModelKairos(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ featureFlagsClassic,
+ )
+ }
- connectionsRepository.activeMobileDataRepository.value = repository
+ private val Kosmos.homeIcon: HomeMobileIconViewModelKairos by
+ Kosmos.Fixture { HomeMobileIconViewModelKairos(commonImpl, mock()) }
- connectivityRepository.apply { setMobileConnected() }
+ private val Kosmos.qsIcon: QsMobileIconViewModelKairos by
+ Kosmos.Fixture { QsMobileIconViewModelKairos(commonImpl) }
- iconsInteractor =
- MobileIconsInteractorImpl(
- connectionsRepository,
- carrierConfigTracker,
- tableLogBuffer,
- connectivityRepository,
- FakeUserSetupRepository(),
- testScope.backgroundScope,
- context,
- flags,
- )
+ private val Kosmos.keyguardIcon: KeyguardMobileIconViewModelKairos by
+ Kosmos.Fixture { KeyguardMobileIconViewModelKairos(commonImpl) }
+
+ private val Kosmos.iconsInteractor: MobileIconsInteractorKairos
+ get() = mobileIconsInteractorKairos
- interactor =
- MobileIconInteractorImpl(
- testScope.backgroundScope,
+ private val Kosmos.interactor: MobileIconInteractorKairos by
+ Kosmos.Fixture {
+ MobileIconInteractorKairosImpl(
iconsInteractor.activeDataConnectionHasDataEnabled,
iconsInteractor.alwaysShowDataRatIcon,
iconsInteractor.alwaysUseCdmaLevel,
@@ -136,50 +95,74 @@ class LocationBasedMobileIconViewModelKairosTest : SysuiTestCase() {
context,
MobileIconCarrierIdOverridesFake(),
)
+ }
- commonImpl =
- MobileIconViewModelKairos(
- SUB_1_ID,
- interactor,
- airplaneModeInteractor,
- constants,
- testScope.backgroundScope,
- )
-
- homeIcon = HomeMobileIconViewModelKairos(commonImpl, mock())
- qsIcon = QsMobileIconViewModelKairos(commonImpl)
- keyguardIcon = KeyguardMobileIconViewModelKairos(commonImpl)
- }
+ private val Kosmos.repository: FakeMobileConnectionRepositoryKairos by
+ Kosmos.Fixture {
+ FakeMobileConnectionRepositoryKairos(SUB_1_ID, kairos, tableLogBuffer).apply {
+ isInService.setValue(true)
+ cdmaLevel.setValue(1)
+ primaryLevel.setValue(1)
+ isEmergencyOnly.setValue(false)
+ numberOfLevels.setValue(4)
+ resolvedNetworkType.setValue(
+ ResolvedNetworkType.DefaultNetworkType(lookupKey = "3G")
+ )
+ dataConnectionState.setValue(DataConnectionState.Connected)
+ }
+ }
- @Test
- fun locationBasedViewModelsReceiveSameIconIdWhenCommonImplUpdates() =
- testScope.runTest {
- var latestHome: SignalIconModel? = null
- val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this)
+ private val Kosmos.constants: ConnectivityConstants by Kosmos.Fixture { mock() }
+ private val Kosmos.tableLogBuffer by
+ Kosmos.Fixture { logcatTableLogBuffer(this, "LocationBasedMobileIconViewModelTest") }
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ mobileConnectionsRepositoryKairos =
+ fakeMobileConnectionsRepositoryKairos.apply {
+ setActiveMobileDataSubscriptionId(SUB_1_ID)
+ subscriptions.setValue(
+ listOf(
+ SubscriptionModel(
+ SUB_1_ID,
+ carrierName = "carrierName",
+ profileClass = 0,
+ )
+ )
+ )
+ }
+ connectivityRepository.fake.apply { setMobileConnected() }
+ featureFlagsClassic.fake.apply {
+ set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ }
+ }
- var latestQs: SignalIconModel? = null
- val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this)
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
- var latestKeyguard: SignalIconModel? = null
- val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this)
+ @Test
+ fun locationBasedViewModelsReceiveSameIconIdWhenCommonImplUpdates() = runTest {
+ repository.dataEnabled.setValue(true)
+ repository.isInService.setValue(true)
- var expected = defaultSignal(level = 1)
+ val latestHome by homeIcon.icon.collectLastValue()
+ val latestQs by qsIcon.icon.collectLastValue()
+ val latestKeyguard by keyguardIcon.icon.collectLastValue()
- assertThat(latestHome).isEqualTo(expected)
- assertThat(latestQs).isEqualTo(expected)
- assertThat(latestKeyguard).isEqualTo(expected)
+ var expected = defaultSignal(level = 1)
- repository.setAllLevels(2)
- expected = defaultSignal(level = 2)
+ assertThat(latestHome).isEqualTo(expected)
+ assertThat(latestQs).isEqualTo(expected)
+ assertThat(latestKeyguard).isEqualTo(expected)
- assertThat(latestHome).isEqualTo(expected)
- assertThat(latestQs).isEqualTo(expected)
- assertThat(latestKeyguard).isEqualTo(expected)
+ repository.setAllLevels(2)
+ expected = defaultSignal(level = 2)
- homeJob.cancel()
- qsJob.cancel()
- keyguardJob.cancel()
- }
+ assertThat(latestHome).isEqualTo(expected)
+ assertThat(latestQs).isEqualTo(expected)
+ assertThat(latestKeyguard).isEqualTo(expected)
+ }
companion object {
private const val SUB_1_ID = 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt
index 6b114a8256f2..68499d1bc57c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -28,1039 +28,893 @@ import com.android.systemui.Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.airplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.fake
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
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.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository.Companion.DEFAULT_NETWORK_NAME
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairos
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractorKairos
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
import com.android.systemui.testKosmos
-import com.android.systemui.util.CarrierConfigTracker
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalKairosApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileIconViewModelKairosTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private var connectivityRepository = FakeConnectivityRepository()
-
- private lateinit var underTest: MobileIconViewModelKairos
- private lateinit var interactor: MobileIconInteractorImpl
- private lateinit var iconsInteractor: MobileIconsInteractorImpl
- private lateinit var repository: FakeMobileConnectionRepository
- private lateinit var connectionsRepository: FakeMobileConnectionsRepository
- private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
- private lateinit var airplaneModeInteractor: AirplaneModeInteractor
- @Mock private lateinit var constants: ConnectivityConstants
- private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconViewModelTest")
- @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
-
- private val flags =
- FakeFeatureFlagsClassic().also {
- it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+
+ private val Kosmos.underTest: MobileIconViewModelKairos by ActivatedKairosFixture {
+ MobileIconViewModelKairos(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ featureFlagsClassic,
+ )
+ }
+ private val Kosmos.interactor: MobileIconInteractorKairos by ActivatedKairosFixture {
+ MobileIconInteractorKairosImpl(
+ mobileIconsInteractorKairos.activeDataConnectionHasDataEnabled,
+ mobileIconsInteractorKairos.alwaysShowDataRatIcon,
+ mobileIconsInteractorKairos.alwaysUseCdmaLevel,
+ mobileIconsInteractorKairos.isSingleCarrier,
+ mobileIconsInteractorKairos.mobileIsDefault,
+ mobileIconsInteractorKairos.defaultMobileIconMapping,
+ mobileIconsInteractorKairos.defaultMobileIconGroup,
+ mobileIconsInteractorKairos.isDefaultConnectionFailed,
+ mobileIconsInteractorKairos.isForceHidden,
+ repository,
+ context,
+ MobileIconCarrierIdOverridesFake(),
+ )
+ }
+ private val Kosmos.repository: FakeMobileConnectionRepositoryKairos by
+ Kosmos.Fixture {
+ FakeMobileConnectionRepositoryKairos(SUB_1_ID, kairos, tableLogBuffer)
+ .also {
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(
+ SUB_1_ID
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(
+ SubscriptionModel(
+ SUB_1_ID,
+ carrierName = "carrierName",
+ profileClass = 0,
+ )
+ )
+ )
+ }
+ .apply {
+ isInService.setValue(true)
+ dataConnectionState.setValue(DataConnectionState.Connected)
+ dataEnabled.setValue(true)
+ setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ }
}
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- whenever(constants.hasDataCapabilities).thenReturn(true)
-
- connectionsRepository =
- FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
-
- repository =
- FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
- setNetworkTypeKey(connectionsRepository.GSM_KEY)
- isInService.value = true
- dataConnectionState.value = DataConnectionState.Connected
- dataEnabled.value = true
+ private val Kosmos.constants: ConnectivityConstants by
+ Kosmos.Fixture { mock { on { hasDataCapabilities } doReturn true } }
+ private val Kosmos.tableLogBuffer by
+ Kosmos.Fixture { logcatTableLogBuffer(this, "MobileIconViewModelKairosTest") }
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ mobileConnectionsRepositoryKairos =
+ fakeMobileConnectionsRepositoryKairos.apply { mobileIsDefault.setValue(true) }
+ featureFlagsClassic.fake.apply {
+ set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
}
- connectionsRepository.activeMobileDataRepository.value = repository
- connectionsRepository.mobileIsDefault.value = true
-
- airplaneModeRepository = FakeAirplaneModeRepository()
- airplaneModeInteractor =
- AirplaneModeInteractor(
- airplaneModeRepository,
- connectivityRepository,
- kosmos.fakeMobileConnectionsRepository,
- )
-
- iconsInteractor =
- MobileIconsInteractorImpl(
- connectionsRepository,
- carrierConfigTracker,
- tableLogBuffer,
- connectivityRepository,
- FakeUserSetupRepository(),
- testScope.backgroundScope,
- context,
- flags,
- )
+ }
- interactor =
- MobileIconInteractorImpl(
- testScope.backgroundScope,
- iconsInteractor.activeDataConnectionHasDataEnabled,
- iconsInteractor.alwaysShowDataRatIcon,
- iconsInteractor.alwaysUseCdmaLevel,
- iconsInteractor.isSingleCarrier,
- iconsInteractor.mobileIsDefault,
- iconsInteractor.defaultMobileIconMapping,
- iconsInteractor.defaultMobileIconGroup,
- iconsInteractor.isDefaultConnectionFailed,
- iconsInteractor.isForceHidden,
- repository,
- context,
- MobileIconCarrierIdOverridesFake(),
- )
- createAndSetViewModel()
- }
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
@Test
- fun isVisible_notDataCapable_alwaysFalse() =
- testScope.runTest {
- // Create a new view model here so the constants are properly read
- whenever(constants.hasDataCapabilities).thenReturn(false)
- createAndSetViewModel()
-
- var latest: Boolean? = null
- val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+ fun isVisible_notDataCapable_alwaysFalse() = runTest {
+ // Create a new view model here so the constants are properly read
+ constants.stub { on { hasDataCapabilities } doReturn false }
- assertThat(latest).isFalse()
+ val latest by underTest.isVisible.collectLastValue()
- job.cancel()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun isVisible_notAirplane_notForceHidden_true() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+ fun isVisible_notAirplane_notForceHidden_true() = runTest {
+ val latest by underTest.isVisible.collectLastValue()
- airplaneModeRepository.setIsAirplaneMode(false)
+ airplaneModeRepository.fake.setIsAirplaneMode(false)
- assertThat(latest).isTrue()
-
- job.cancel()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun isVisible_airplaneAndNotAllowed_false() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
-
- airplaneModeRepository.setIsAirplaneMode(true)
- repository.isAllowedDuringAirplaneMode.value = false
- connectivityRepository.setForceHiddenIcons(setOf())
+ fun isVisible_airplaneAndNotAllowed_false() = runTest {
+ val latest by underTest.isVisible.collectLastValue()
- assertThat(latest).isFalse()
+ airplaneModeRepository.fake.setIsAirplaneMode(true)
+ repository.isAllowedDuringAirplaneMode.setValue(false)
+ connectivityRepository.fake.setForceHiddenIcons(setOf())
- job.cancel()
- }
+ assertThat(latest).isEqualTo(false)
+ }
/** Regression test for b/291993542. */
@Test
- fun isVisible_airplaneButAllowed_true() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
-
- airplaneModeRepository.setIsAirplaneMode(true)
- repository.isAllowedDuringAirplaneMode.value = true
- connectivityRepository.setForceHiddenIcons(setOf())
+ fun isVisible_airplaneButAllowed_true() = runTest {
+ val latest by underTest.isVisible.collectLastValue()
- assertThat(latest).isTrue()
+ airplaneModeRepository.fake.setIsAirplaneMode(true)
+ repository.isAllowedDuringAirplaneMode.setValue(true)
+ connectivityRepository.fake.setForceHiddenIcons(setOf())
- job.cancel()
- }
+ assertThat(latest).isTrue()
+ }
@Test
- fun isVisible_forceHidden_false() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+ fun isVisible_forceHidden_false() = runTest {
+ val latest by underTest.isVisible.collectLastValue()
- airplaneModeRepository.setIsAirplaneMode(false)
- connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+ airplaneModeRepository.fake.setIsAirplaneMode(false)
+ connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
- assertThat(latest).isFalse()
-
- job.cancel()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun isVisible_respondsToUpdates() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
-
- airplaneModeRepository.setIsAirplaneMode(false)
- connectivityRepository.setForceHiddenIcons(setOf())
+ fun isVisible_respondsToUpdates() = runTest {
+ val latest by underTest.isVisible.collectLastValue()
- assertThat(latest).isTrue()
+ airplaneModeRepository.fake.setIsAirplaneMode(false)
+ connectivityRepository.fake.setForceHiddenIcons(setOf())
- airplaneModeRepository.setIsAirplaneMode(true)
- assertThat(latest).isFalse()
+ assertThat(latest).isEqualTo(true)
- repository.isAllowedDuringAirplaneMode.value = true
- assertThat(latest).isTrue()
+ airplaneModeRepository.fake.setIsAirplaneMode(true)
+ assertThat(latest).isEqualTo(false)
- connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
- assertThat(latest).isFalse()
+ repository.isAllowedDuringAirplaneMode.setValue(true)
+ assertThat(latest).isEqualTo(true)
- job.cancel()
- }
+ connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+ assertThat(latest).isEqualTo(false)
+ }
@Test
- fun isVisible_satellite_respectsAirplaneMode() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isVisible)
+ fun isVisible_satellite_respectsAirplaneMode() = runTest {
+ val latest by underTest.isVisible.collectLastValue()
- repository.isNonTerrestrial.value = true
- airplaneModeInteractor.setIsAirplaneMode(false)
+ repository.isNonTerrestrial.setValue(true)
+ airplaneModeInteractor.setIsAirplaneMode(false)
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- airplaneModeInteractor.setIsAirplaneMode(true)
+ airplaneModeInteractor.setIsAirplaneMode(true)
- assertThat(latest).isFalse()
- }
+ assertThat(latest).isFalse()
+ }
@Test
- fun contentDescription_notInService_usesNoPhone() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
+ fun contentDescription_notInService_usesNoPhone() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
- repository.isInService.value = false
+ repository.isInService.setValue(false)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
- }
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
@Test
- fun contentDescription_includesNetworkName() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
+ fun contentDescription_includesNetworkName() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
- repository.isInService.value = true
- repository.networkName.value = NetworkNameModel.SubscriptionDerived("Test Network Name")
- repository.numberOfLevels.value = 5
- repository.setAllLevels(3)
+ repository.isInService.setValue(true)
+ repository.networkName.setValue(NetworkNameModel.SubscriptionDerived("Test Network Name"))
+ repository.numberOfLevels.setValue(5)
+ repository.setAllLevels(3)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular("Test Network Name", THREE_BARS))
- }
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular("Test Network Name", THREE_BARS))
+ }
@Test
- fun contentDescription_inService_usesLevel() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
+ fun contentDescription_inService_usesLevel() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
- repository.setAllLevels(2)
+ repository.setAllLevels(2)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
- repository.setAllLevels(0)
+ repository.setAllLevels(0)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
- }
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
@Test
- fun contentDescription_nonInflated_invalidLevelUsesNoSignalText() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
+ fun contentDescription_nonInflated_invalidLevelUsesNoSignalText() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
- repository.inflateSignalStrength.value = false
- repository.setAllLevels(-1)
+ repository.inflateSignalStrength.setValue(false)
+ repository.setAllLevels(-1)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
- repository.setAllLevels(100)
+ repository.setAllLevels(100)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
- }
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
@Test
- fun contentDescription_nonInflated_levelStrings() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
+ fun contentDescription_nonInflated_levelStrings() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
- repository.inflateSignalStrength.value = false
- repository.setAllLevels(0)
+ repository.inflateSignalStrength.setValue(false)
+ repository.setAllLevels(0)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
- repository.setAllLevels(1)
+ repository.setAllLevels(1)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR))
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR))
- repository.setAllLevels(2)
+ repository.setAllLevels(2)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
- repository.setAllLevels(3)
+ repository.setAllLevels(3)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS))
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS))
- repository.setAllLevels(4)
+ repository.setAllLevels(4)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS))
- }
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS))
+ }
@Test
- fun contentDescription_inflated_invalidLevelUsesNoSignalText() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
+ fun contentDescription_inflated_invalidLevelUsesNoSignalText() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
- repository.inflateSignalStrength.value = true
- repository.numberOfLevels.value = 6
+ repository.inflateSignalStrength.setValue(true)
+ repository.numberOfLevels.setValue(6)
+ repository.setAllLevels(-2)
- repository.setAllLevels(-2)
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ repository.setAllLevels(100)
- repository.setAllLevels(100)
-
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
- }
+ assertThat(latest as MobileContentDescription.Cellular)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL))
+ }
@Test
- fun contentDescription_inflated_levelStrings() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
+ fun contentDescription_inflated_levelStrings() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
- repository.inflateSignalStrength.value = true
- repository.numberOfLevels.value = 6
+ repository.inflateSignalStrength.setValue(true)
+ repository.numberOfLevels.setValue(6)
- // Note that the _repo_ level is 1 lower than the reported level through the interactor
+ // Note that the _repo_ level is 1 lower than the reported level through the interactor
- repository.setAllLevels(0)
+ repository.setAllLevels(0)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR))
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR))
- repository.setAllLevels(1)
+ repository.setAllLevels(1)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS))
- repository.setAllLevels(2)
+ repository.setAllLevels(2)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS))
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS))
- repository.setAllLevels(3)
+ repository.setAllLevels(3)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FOUR_BARS))
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FOUR_BARS))
- repository.setAllLevels(4)
+ repository.setAllLevels(4)
- assertThat(latest as MobileContentDescription.Cellular)
- .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS))
- }
+ assertThat(latest)
+ .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS))
+ }
@Test
- fun contentDescription_nonInflated_testABunchOfLevelsForNull() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
-
- repository.inflateSignalStrength.value = false
- repository.numberOfLevels.value = 5
-
- // -1 and 5 are out of the bounds for non-inflated content descriptions
- for (i in -1..5) {
- repository.setAllLevels(i)
- when (i) {
- -1,
- 5 ->
- assertWithMessage("Level $i is expected to be 'no signal'")
- .that((latest as MobileContentDescription.Cellular).levelDescriptionRes)
- .isEqualTo(NO_SIGNAL)
- else ->
- assertWithMessage("Level $i is expected not to be null")
- .that(latest)
- .isNotNull()
- }
+ fun contentDescription_nonInflated_testABunchOfLevelsForNull() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
+
+ repository.inflateSignalStrength.setValue(false)
+ repository.numberOfLevels.setValue(5)
+
+ // -1 and 5 are out of the bounds for non-inflated content descriptions
+ for (i in -1..5) {
+ repository.setAllLevels(i)
+ when (i) {
+ -1,
+ 5 ->
+ assertWithMessage("Level $i is expected to be null")
+ .that((latest as MobileContentDescription.Cellular).levelDescriptionRes)
+ .isEqualTo(NO_SIGNAL)
+ else ->
+ assertWithMessage("Level $i is expected not to be null")
+ .that(latest)
+ .isNotNull()
}
}
+ }
@Test
- fun contentDescription_inflated_testABunchOfLevelsForNull() =
- testScope.runTest {
- val latest by collectLastValue(underTest.contentDescription)
- repository.inflateSignalStrength.value = true
- repository.numberOfLevels.value = 6
- // -1 and 6 are out of the bounds for inflated content descriptions
- // Note that the interactor adds 1 to the reported level, hence the -2 to 5 range
- for (i in -2..5) {
- repository.setAllLevels(i)
- when (i) {
- -2,
- 5 ->
- assertWithMessage("Level $i is expected to be 'no signal'")
- .that((latest as MobileContentDescription.Cellular).levelDescriptionRes)
- .isEqualTo(NO_SIGNAL)
- else ->
- assertWithMessage("Level $i is not expected to be null")
- .that(latest)
- .isNotNull()
- }
+ fun contentDescription_inflated_testABunchOfLevelsForNull() = runTest {
+ val latest by underTest.contentDescription.collectLastValue()
+ repository.inflateSignalStrength.setValue(true)
+ repository.numberOfLevels.setValue(6)
+ // -1 and 6 are out of the bounds for inflated content descriptions
+ // Note that the interactor adds 1 to the reported level, hence the -2 to 5 range
+ for (i in -2..5) {
+ repository.setAllLevels(i)
+ when (i) {
+ -2,
+ 5 ->
+ assertWithMessage("Level $i is expected to be null")
+ .that((latest as MobileContentDescription.Cellular).levelDescriptionRes)
+ .isEqualTo(NO_SIGNAL)
+ else ->
+ assertWithMessage("Level $i is not expected to be null")
+ .that(latest)
+ .isNotNull()
}
}
+ }
@Test
- fun networkType_dataEnabled_groupIsRepresented() =
- testScope.runTest {
- val expected =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
- connectionsRepository.mobileIsDefault.value = true
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
-
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
-
- @Test
- fun networkType_null_whenDisabled() =
- testScope.runTest {
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- repository.setDataEnabled(false)
- connectionsRepository.mobileIsDefault.value = true
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+ fun networkType_dataEnabled_groupIsRepresented() = runTest {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
- assertThat(latest).isNull()
+ val latest by underTest.networkTypeIcon.collectLastValue()
- job.cancel()
- }
+ assertThat(latest).isEqualTo(expected)
+ }
@Test
- fun networkType_null_whenCarrierNetworkChangeActive() =
- testScope.runTest {
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- repository.carrierNetworkChangeActive.value = true
- connectionsRepository.mobileIsDefault.value = true
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isNull()
+ fun networkType_null_whenDisabled() = runTest {
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ repository.dataEnabled.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- job.cancel()
- }
+ assertThat(latest).isNull()
+ }
@Test
- fun networkTypeIcon_notNull_whenEnabled() =
- testScope.runTest {
- val expected =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- repository.setDataEnabled(true)
- repository.dataConnectionState.value = DataConnectionState.Connected
- connectionsRepository.mobileIsDefault.value = true
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
+ fun networkType_null_whenCarrierNetworkChangeActive() = runTest {
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ repository.carrierNetworkChangeActive.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ val latest by underTest.networkTypeIcon.collectLastValue()
+
+ assertThat(latest).isNull()
+ }
@Test
- fun networkType_nullWhenDataDisconnects() =
- testScope.runTest {
- val initial =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
+ fun networkTypeIcon_notNull_whenEnabled() = runTest {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ repository.dataEnabled.setValue(true)
+ repository.dataConnectionState.setValue(DataConnectionState.Connected)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isEqualTo(expected)
+ }
- assertThat(latest).isEqualTo(initial)
+ @Test
+ fun networkType_nullWhenDataDisconnects() = runTest {
+ val initial =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
- repository.dataConnectionState.value = DataConnectionState.Disconnected
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- assertThat(latest).isNull()
+ assertThat(latest).isEqualTo(initial)
- job.cancel()
- }
+ repository.dataConnectionState.setValue(DataConnectionState.Disconnected)
- @Test
- fun networkType_null_changeToDisabled() =
- testScope.runTest {
- val expected =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
- repository.dataEnabled.value = true
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isNull()
+ }
- assertThat(latest).isEqualTo(expected)
+ @Test
+ fun networkType_null_changeToDisabled() = runTest {
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ repository.dataEnabled.setValue(true)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- repository.dataEnabled.value = false
+ assertThat(latest).isEqualTo(expected)
- assertThat(latest).isNull()
+ repository.dataEnabled.setValue(false)
- job.cancel()
- }
+ assertThat(latest).isNull()
+ }
@Test
- fun networkType_alwaysShow_shownEvenWhenDisabled() =
- testScope.runTest {
- repository.dataEnabled.value = false
-
- connectionsRepository.defaultDataSubRatConfig.value =
- MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+ fun networkType_alwaysShow_shownEvenWhenDisabled() = runTest {
+ repository.dataEnabled.setValue(false)
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+ )
- val expected =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
- assertThat(latest).isEqualTo(expected)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- job.cancel()
- }
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+ }
@Test
- fun networkType_alwaysShow_shownEvenWhenDisconnected() =
- testScope.runTest {
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- repository.dataConnectionState.value = DataConnectionState.Disconnected
+ fun networkType_alwaysShow_shownEvenWhenDisconnected() = runTest {
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ repository.dataConnectionState.setValue(DataConnectionState.Disconnected)
- connectionsRepository.defaultDataSubRatConfig.value =
- MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+ )
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- val expected =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+ }
@Test
- fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
- testScope.runTest {
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- connectionsRepository.mobileIsDefault.value = true
- connectionsRepository.defaultDataSubRatConfig.value =
- MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
-
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
-
- val expected =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
+ fun networkType_alwaysShow_shownEvenWhenFailedConnection() = runTest {
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+ )
- @Test
- fun networkType_alwaysShow_usesDefaultIconWhenInvalid() =
- testScope.runTest {
- // The UNKNOWN icon group doesn't have a valid data type icon ID, and the logic from the
- // old pipeline was to use the default icon group if the map doesn't exist
- repository.setNetworkTypeKey(UNKNOWN.name)
- connectionsRepository.defaultDataSubRatConfig.value =
- MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
-
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
-
- val expected =
- Icon.Resource(
- connectionsRepository.defaultMobileIconGroup.value.dataType,
- ContentDescription.Resource(G.dataContentDescription),
- )
-
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
+ val latest by underTest.networkTypeIcon.collectLastValue()
- @Test
- fun networkType_alwaysShow_shownWhenNotDefault() =
- testScope.runTest {
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- connectionsRepository.mobileIsDefault.value = false
- connectionsRepository.defaultDataSubRatConfig.value =
- MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
-
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
-
- val expected =
- Icon.Resource(
- THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription),
- )
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+ }
@Test
- fun networkType_notShownWhenNotDefault() =
- testScope.runTest {
- repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
- repository.dataConnectionState.value = DataConnectionState.Connected
- connectionsRepository.mobileIsDefault.value = false
+ fun networkType_alwaysShow_usesDefaultIconWhenInvalid() = runTest {
+ // The UNKNOWN icon group doesn't have a valid data type icon ID, and the logic from the
+ // old pipeline was to use the default icon group if the map doesn't exist
+ repository.setNetworkTypeKey(UNKNOWN.name)
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+ )
- var latest: Icon? = null
- val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- assertThat(latest).isNull()
+ val expected =
+ Icon.Resource(
+ kairos.transact {
+ mobileConnectionsRepositoryKairos.fake.defaultMobileIconGroup.sample().dataType
+ },
+ ContentDescription.Resource(G.dataContentDescription),
+ )
- job.cancel()
- }
+ assertThat(latest).isEqualTo(expected)
+ }
@Test
- fun roaming() =
- testScope.runTest {
- repository.setAllRoaming(true)
+ fun networkType_alwaysShow_shownWhenNotDefault() = runTest {
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
+ )
- var latest: Boolean? = null
- val job = underTest.roaming.onEach { latest = it }.launchIn(this)
+ val latest by underTest.networkTypeIcon.collectLastValue()
- assertThat(latest).isTrue()
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription),
+ )
+ assertThat(latest).isEqualTo(expected)
+ }
- repository.setAllRoaming(false)
+ @Test
+ fun networkType_notShownWhenNotDefault() = runTest {
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ repository.dataConnectionState.setValue(DataConnectionState.Connected)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
- assertThat(latest).isFalse()
+ val latest by underTest.networkTypeIcon.collectLastValue()
- job.cancel()
- }
+ assertThat(latest).isNull()
+ }
@Test
- fun dataActivity_nullWhenConfigIsOff() =
- testScope.runTest {
- // Create a new view model here so the constants are properly read
- whenever(constants.shouldShowActivityConfig).thenReturn(false)
- createAndSetViewModel()
+ fun roaming() = runTest {
+ repository.setAllRoaming(true)
- var inVisible: Boolean? = null
- val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+ val latest by underTest.roaming.collectLastValue()
- var outVisible: Boolean? = null
- val outJob = underTest.activityInVisible.onEach { outVisible = it }.launchIn(this)
+ assertThat(latest).isTrue()
- var containerVisible: Boolean? = null
- val containerJob =
- underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
+ repository.setAllRoaming(false)
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ assertThat(latest).isFalse()
+ }
- assertThat(inVisible).isFalse()
- assertThat(outVisible).isFalse()
- assertThat(containerVisible).isFalse()
+ @Test
+ fun dataActivity_nullWhenConfigIsOff() = runTest {
+ constants.stub { on { shouldShowActivityConfig } doReturn false }
- inJob.cancel()
- outJob.cancel()
- containerJob.cancel()
- }
+ val inVisible by underTest.activityInVisible.collectLastValue()
+
+ val outVisible by underTest.activityInVisible.collectLastValue()
+
+ val containerVisible by underTest.activityInVisible.collectLastValue()
+
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ )
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isFalse()
+ }
@Test
@DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
- fun dataActivity_configOn_testIndicators_staticFlagOff() =
- testScope.runTest {
- // Create a new view model here so the constants are properly read
- whenever(constants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
+ fun dataActivity_configOn_testIndicators_staticFlagOff() = runTest {
+ constants.stub { on { shouldShowActivityConfig } doReturn true }
- var inVisible: Boolean? = null
- val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+ val inVisible by underTest.activityInVisible.collectLastValue()
- var outVisible: Boolean? = null
- val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+ val outVisible by underTest.activityOutVisible.collectLastValue()
- var containerVisible: Boolean? = null
- val containerJob =
- underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+ val containerVisible by underTest.activityContainerVisible.collectLastValue()
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ )
- yield()
+ yield()
- assertThat(inVisible).isTrue()
- assertThat(outVisible).isFalse()
- assertThat(containerVisible).isTrue()
+ assertThat(inVisible).isTrue()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ )
- assertThat(inVisible).isFalse()
- assertThat(outVisible).isTrue()
- assertThat(containerVisible).isTrue()
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isTrue()
+ assertThat(containerVisible).isTrue()
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ )
- assertThat(inVisible).isFalse()
- assertThat(outVisible).isFalse()
- assertThat(containerVisible).isFalse()
-
- inJob.cancel()
- outJob.cancel()
- containerJob.cancel()
- }
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isFalse()
+ }
@Test
@EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
- fun dataActivity_configOn_testIndicators_staticFlagOn() =
- testScope.runTest {
- // Create a new view model here so the constants are properly read
- whenever(constants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
-
- var inVisible: Boolean? = null
- val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+ fun dataActivity_configOn_testIndicators_staticFlagOn() = runTest {
+ constants.stub { on { shouldShowActivityConfig } doReturn true }
- var outVisible: Boolean? = null
- val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+ val inVisible by underTest.activityInVisible.collectLastValue()
- var containerVisible: Boolean? = null
- val containerJob =
- underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+ val outVisible by underTest.activityOutVisible.collectLastValue()
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ val containerVisible by underTest.activityContainerVisible.collectLastValue()
- yield()
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ )
- assertThat(inVisible).isTrue()
- assertThat(outVisible).isFalse()
- assertThat(containerVisible).isTrue()
+ yield()
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ assertThat(inVisible).isTrue()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
- assertThat(inVisible).isFalse()
- assertThat(outVisible).isTrue()
- assertThat(containerVisible).isTrue()
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ )
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isTrue()
+ assertThat(containerVisible).isTrue()
- assertThat(inVisible).isFalse()
- assertThat(outVisible).isFalse()
- assertThat(containerVisible).isTrue()
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ )
- inJob.cancel()
- outJob.cancel()
- containerJob.cancel()
- }
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
+ }
@Test
- fun netTypeBackground_nullWhenNoPrioritizedCapabilities() =
- testScope.runTest {
- createAndSetViewModel()
-
- val latest by collectLastValue(underTest.networkTypeBackground)
+ fun netTypeBackground_nullWhenNoPrioritizedCapabilities() = runTest {
+ val latest by underTest.networkTypeBackground.collectLastValue()
- repository.hasPrioritizedNetworkCapabilities.value = false
+ repository.hasPrioritizedNetworkCapabilities.setValue(false)
- assertThat(latest).isNull()
- }
+ assertThat(latest).isNull()
+ }
@Test
@EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun netTypeBackground_sliceUiEnabled_notNullWhenPrioritizedCapabilities_newIcons() =
- testScope.runTest {
- createAndSetViewModel()
+ fun netTypeBackground_sliceUiEnabled_notNullWhenPrioritizedCapabilities_newIcons() = runTest {
+ val latest by underTest.networkTypeBackground.collectLastValue()
- val latest by collectLastValue(underTest.networkTypeBackground)
+ repository.hasPrioritizedNetworkCapabilities.setValue(true)
- repository.hasPrioritizedNetworkCapabilities.value = true
-
- assertThat(latest)
- .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background_updated, null))
- }
+ assertThat(latest)
+ .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background_updated, null))
+ }
@Test
@DisableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
- fun netTypeBackground_sliceUiDisabled_notNullWhenPrioritizedCapabilities_oldIcons() =
- testScope.runTest {
- createAndSetViewModel()
-
- val latest by collectLastValue(underTest.networkTypeBackground)
+ fun netTypeBackground_sliceUiDisabled_notNullWhenPrioritizedCapabilities_oldIcons() = runTest {
+ val latest by underTest.networkTypeBackground.collectLastValue()
- repository.hasPrioritizedNetworkCapabilities.value = true
+ repository.allowNetworkSliceIndicator.setValue(true)
+ repository.hasPrioritizedNetworkCapabilities.setValue(true)
- assertThat(latest)
- .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
- }
+ assertThat(latest).isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
+ }
@Test
- fun nonTerrestrial_defaultProperties() =
- testScope.runTest {
- repository.isNonTerrestrial.value = true
-
- val roaming by collectLastValue(underTest.roaming)
- val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
- val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
- val activityInVisible by collectLastValue(underTest.activityInVisible)
- val activityOutVisible by collectLastValue(underTest.activityOutVisible)
- val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
-
- assertThat(roaming).isFalse()
- assertThat(networkTypeIcon).isNull()
- assertThat(networkTypeBackground).isNull()
- assertThat(activityInVisible).isFalse()
- assertThat(activityOutVisible).isFalse()
- assertThat(activityContainerVisible).isFalse()
- }
+ fun nonTerrestrial_defaultProperties() = runTest {
+ repository.isNonTerrestrial.setValue(true)
+
+ val roaming by underTest.roaming.collectLastValue()
+ val networkTypeIcon by underTest.networkTypeIcon.collectLastValue()
+ val networkTypeBackground by underTest.networkTypeBackground.collectLastValue()
+ val activityInVisible by underTest.activityInVisible.collectLastValue()
+ val activityOutVisible by underTest.activityOutVisible.collectLastValue()
+ val activityContainerVisible by underTest.activityContainerVisible.collectLastValue()
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
@Test
- fun nonTerrestrial_ignoresDefaultProperties() =
- testScope.runTest {
- repository.isNonTerrestrial.value = true
-
- val roaming by collectLastValue(underTest.roaming)
- val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
- val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
- val activityInVisible by collectLastValue(underTest.activityInVisible)
- val activityOutVisible by collectLastValue(underTest.activityOutVisible)
- val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
-
- repository.setAllRoaming(true)
- repository.setNetworkTypeKey(connectionsRepository.LTE_KEY)
- // sets the background on cellular
- repository.hasPrioritizedNetworkCapabilities.value = true
- repository.dataActivityDirection.value =
- DataActivityModel(hasActivityIn = true, hasActivityOut = true)
-
- assertThat(roaming).isFalse()
- assertThat(networkTypeIcon).isNull()
- assertThat(networkTypeBackground).isNull()
- assertThat(activityInVisible).isFalse()
- assertThat(activityOutVisible).isFalse()
- assertThat(activityContainerVisible).isFalse()
- }
+ fun nonTerrestrial_ignoresDefaultProperties() = runTest {
+ repository.isNonTerrestrial.setValue(true)
+
+ val roaming by underTest.roaming.collectLastValue()
+ val networkTypeIcon by underTest.networkTypeIcon.collectLastValue()
+ val networkTypeBackground by underTest.networkTypeBackground.collectLastValue()
+ val activityInVisible by underTest.activityInVisible.collectLastValue()
+ val activityOutVisible by underTest.activityOutVisible.collectLastValue()
+ val activityContainerVisible by underTest.activityContainerVisible.collectLastValue()
+
+ repository.setAllRoaming(true)
+ repository.setNetworkTypeKey(mobileConnectionsRepositoryKairos.fake.LTE_KEY)
+ // sets the background on cellular
+ repository.hasPrioritizedNetworkCapabilities.setValue(true)
+ repository.dataActivityDirection.setValue(
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ )
+
+ assertThat(roaming).isFalse()
+ assertThat(networkTypeIcon).isNull()
+ assertThat(networkTypeBackground).isNull()
+ assertThat(activityInVisible).isFalse()
+ assertThat(activityOutVisible).isFalse()
+ assertThat(activityContainerVisible).isFalse()
+ }
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun nonTerrestrial_usesSatelliteIcon_flagOff() =
- testScope.runTest {
- repository.isNonTerrestrial.value = true
- repository.setAllLevels(0)
- repository.satelliteLevel.value = 0
-
- val latest by
- collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
-
- // Level 0 -> no connection
- assertThat(latest).isNotNull()
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
-
- // 1-2 -> 1 bar
- repository.setAllLevels(1)
- repository.satelliteLevel.value = 1
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
-
- repository.setAllLevels(2)
- repository.satelliteLevel.value = 2
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
-
- // 3-4 -> 2 bars
- repository.setAllLevels(3)
- repository.satelliteLevel.value = 3
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
-
- repository.setAllLevels(4)
- repository.satelliteLevel.value = 4
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
- }
+ fun nonTerrestrial_usesSatelliteIcon_flagOff() = runTest {
+ repository.isNonTerrestrial.setValue(true)
+ repository.setAllLevels(0)
+ repository.satelliteLevel.setValue(0)
+
+ val latest by underTest.icon.map { it as SignalIconModel.Satellite }.collectLastValue()
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.setAllLevels(1)
+ repository.satelliteLevel.setValue(1)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.setAllLevels(2)
+ repository.satelliteLevel.setValue(2)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.setAllLevels(3)
+ repository.satelliteLevel.setValue(3)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.setAllLevels(4)
+ repository.satelliteLevel.setValue(4)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun nonTerrestrial_usesSatelliteIcon_flagOn() =
- testScope.runTest {
- repository.isNonTerrestrial.value = true
- repository.satelliteLevel.value = 0
+ fun nonTerrestrial_usesSatelliteIcon_flagOn() = runTest {
+ repository.isNonTerrestrial.setValue(true)
+ repository.satelliteLevel.setValue(0)
- val latest by
- collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+ val latest by underTest.icon.map { it as SignalIconModel.Satellite }.collectLastValue()
- // Level 0 -> no connection
- assertThat(latest).isNotNull()
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
- // 1-2 -> 1 bar
- repository.satelliteLevel.value = 1
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+ // 1-2 -> 1 bar
+ repository.satelliteLevel.setValue(1)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
- repository.satelliteLevel.value = 2
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+ repository.satelliteLevel.setValue(2)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
- // 3-4 -> 2 bars
- repository.satelliteLevel.value = 3
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ // 3-4 -> 2 bars
+ repository.satelliteLevel.setValue(3)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
- repository.satelliteLevel.value = 4
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
- }
+ repository.satelliteLevel.setValue(4)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
@DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun satelliteIcon_ignoresInflateSignalStrength_flagOff() =
- testScope.runTest {
- // Note that this is the exact same test as above, but with inflateSignalStrength set to
- // true we note that the level is unaffected by inflation
- repository.inflateSignalStrength.value = true
- repository.isNonTerrestrial.value = true
- repository.setAllLevels(0)
- repository.satelliteLevel.value = 0
-
- val latest by
- collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
-
- // Level 0 -> no connection
- assertThat(latest).isNotNull()
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
-
- // 1-2 -> 1 bar
- repository.setAllLevels(1)
- repository.satelliteLevel.value = 1
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
-
- repository.setAllLevels(2)
- repository.satelliteLevel.value = 2
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
-
- // 3-4 -> 2 bars
- repository.setAllLevels(3)
- repository.satelliteLevel.value = 3
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
-
- repository.setAllLevels(4)
- repository.satelliteLevel.value = 4
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
- }
+ fun satelliteIcon_ignoresInflateSignalStrength_flagOff() = runTest {
+ // Note that this is the exact same test as above, but with inflateSignalStrength set to
+ // true we note that the level is unaffected by inflation
+ repository.inflateSignalStrength.setValue(true)
+ repository.isNonTerrestrial.setValue(true)
+ repository.setAllLevels(0)
+ repository.satelliteLevel.setValue(0)
+
+ val latest by underTest.icon.map { it as SignalIconModel.Satellite }.collectLastValue()
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.setAllLevels(1)
+ repository.satelliteLevel.setValue(1)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.setAllLevels(2)
+ repository.satelliteLevel.setValue(2)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.setAllLevels(3)
+ repository.satelliteLevel.setValue(3)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.setAllLevels(4)
+ repository.satelliteLevel.setValue(4)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun satelliteIcon_ignoresInflateSignalStrength_flagOn() =
- testScope.runTest {
- // Note that this is the exact same test as above, but with inflateSignalStrength set to
- // true we note that the level is unaffected by inflation
- repository.inflateSignalStrength.value = true
- repository.isNonTerrestrial.value = true
- repository.satelliteLevel.value = 0
-
- val latest by
- collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
-
- // Level 0 -> no connection
- assertThat(latest).isNotNull()
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
-
- // 1-2 -> 1 bar
- repository.satelliteLevel.value = 1
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
-
- repository.satelliteLevel.value = 2
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
-
- // 3-4 -> 2 bars
- repository.satelliteLevel.value = 3
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
-
- repository.satelliteLevel.value = 4
- assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
- }
+ fun satelliteIcon_ignoresInflateSignalStrength_flagOn() = runTest {
+ // Note that this is the exact same test as above, but with inflateSignalStrength set to
+ // true we note that the level is unaffected by inflation
+ repository.inflateSignalStrength.setValue(true)
+ repository.isNonTerrestrial.setValue(true)
+ repository.satelliteLevel.setValue(0)
- private fun createAndSetViewModel() {
- underTest =
- MobileIconViewModelKairos(
- SUB_1_ID,
- interactor,
- airplaneModeInteractor,
- constants,
- testScope.backgroundScope,
- )
+ val latest by underTest.icon.map { it as SignalIconModel.Satellite }.collectLastValue()
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.satelliteLevel.setValue(1)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.satelliteLevel.setValue(2)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.satelliteLevel.setValue(3)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.satelliteLevel.setValue(4)
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt
index e921430394c2..b11bad6f3ad3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -16,348 +16,323 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
-import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.isActive
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalKairosApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileIconsViewModelKairosTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private lateinit var underTest: MobileIconsViewModelKairos
- private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic()
-
- private lateinit var airplaneModeInteractor: AirplaneModeInteractor
- @Mock private lateinit var constants: ConnectivityConstants
- @Mock private lateinit var logger: MobileViewLogger
- @Mock private lateinit var verboseLogger: VerboseMobileViewLogger
-
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val Kosmos.underTest
+ get() = mobileIconsViewModelKairos
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ featureFlagsClassic.fake.apply {
+ setDefault(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)
+ }
+ mobileConnectionsRepositoryKairos =
+ fakeMobileConnectionsRepositoryKairos.apply {
+ val subList = listOf(SUB_1, SUB_2)
+ setActiveMobileDataSubscriptionId(SUB_1.subscriptionId)
+ subscriptions.setValue(subList)
+ }
+ }
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ private fun KairosTestScope.setSubscriptions(
+ subList: List<SubscriptionModel>,
+ activeSubId: Int = subList.getOrNull(0)?.subscriptionId ?: INVALID_SUBSCRIPTION_ID,
+ ) {
+ println("setSubscriptions: mobileConnectionsRepositoryKairos.fake.subscriptions")
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(subList)
+ println(
+ "setSubscriptions: mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId"
+ )
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(activeSubId)
+ }
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- kosmos.fakeMobileConnectionsRepository,
+ @Test
+ fun subscriptionIdsFlow_matchesInteractor() = runTest {
+ val latest by underTest.subscriptionIds.collectLastValue()
+ setSubscriptions(
+ listOf(
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
)
-
- underTest =
- MobileIconsViewModelKairos(
- logger,
- verboseLogger,
- interactor,
- airplaneModeInteractor,
- constants,
- testScope.backgroundScope,
+ )
+ assertThat(latest).isEqualTo(listOf(1))
+
+ setSubscriptions(
+ listOf(
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ SubscriptionModel(
+ subscriptionId = 5,
+ isOpportunistic = true,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ SubscriptionModel(
+ subscriptionId = 7,
+ isOpportunistic = true,
+ carrierName = "Carrier 7",
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
)
+ )
+ assertThat(latest).isEqualTo(listOf(2, 5, 7))
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ setSubscriptions(emptyList())
+ assertThat(latest).isEmpty()
}
@Test
- fun subscriptionIdsFlow_matchesInteractor() =
- testScope.runTest {
- var latest: List<Int>? = null
- val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this)
-
- interactor.filteredSubscriptions.value =
- listOf(
- SubscriptionModel(
- subscriptionId = 1,
- isOpportunistic = false,
- carrierName = "Carrier 1",
- profileClass = PROFILE_CLASS_UNSET,
- )
- )
- assertThat(latest).isEqualTo(listOf(1))
-
- interactor.filteredSubscriptions.value =
- listOf(
- SubscriptionModel(
- subscriptionId = 2,
- isOpportunistic = false,
- carrierName = "Carrier 2",
- profileClass = PROFILE_CLASS_UNSET,
- ),
- SubscriptionModel(
- subscriptionId = 5,
- isOpportunistic = true,
- carrierName = "Carrier 5",
- profileClass = PROFILE_CLASS_UNSET,
- ),
- SubscriptionModel(
- subscriptionId = 7,
- isOpportunistic = true,
- carrierName = "Carrier 7",
- profileClass = PROFILE_CLASS_UNSET,
- ),
- )
- assertThat(latest).isEqualTo(listOf(2, 5, 7))
+ fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
- interactor.filteredSubscriptions.value = emptyList()
- assertThat(latest).isEmpty()
+ setSubscriptions(emptyList())
- job.cancel()
- }
+ assertThat(latest).isEqualTo(false)
+ }
@Test
- fun caching_mobileIconViewModelIsReusedForSameSubId() =
- testScope.runTest {
- val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
- val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS)
+ fun firstMobileSubShowingNetworkTypeIcon_oneSub_notShowingRat_false() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
- assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl)
- }
+ setSubscriptions(listOf(SUB_1))
+
+ // The unknown icon group doesn't show a RAT
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1.subscriptionId]
+ ?.resolvedNetworkType
+ ?.setValue(UnknownNetworkType)
+
+ assertThat(latest).isFalse()
+ }
@Test
- fun caching_invalidViewModelsAreRemovedFromCacheWhenSubDisappears() =
- testScope.runTest {
- // Retrieve models to trigger caching
- val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
- val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+ fun firstMobileSubShowingNetworkTypeIcon_oneSub_showingRat_true() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
+ setSubscriptions(listOf(SUB_1))
- // Both impls are cached
- assertThat(underTest.reuseCache.keys).containsExactly(1, 2)
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
- // SUB_1 is removed from the list...
- interactor.filteredSubscriptions.value = listOf(SUB_2)
+ // The 3G icon group will show a RAT
+ val repo =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1.subscriptionId]!!
- // ... and dropped from the cache
- assertThat(underTest.reuseCache.keys).containsExactly(2)
- }
+ repo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ )
+ repo.dataConnectionState.setValue(DataConnectionState.Connected)
+
+ assertThat(latest).isEqualTo(true)
+ }
@Test
- fun caching_invalidatedViewModelsAreCanceled() =
- testScope.runTest {
- // Retrieve models to trigger caching
- val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
- val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+ fun firstMobileSubShowingNetworkTypeIcon_updatesAsSubUpdates() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
+ setSubscriptions(listOf(SUB_1))
- var scope1 = underTest.reuseCache[1]?.second
- var scope2 = underTest.reuseCache[2]?.second
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
- // Scopes are not canceled
- assertTrue(scope1!!.isActive)
- assertTrue(scope2!!.isActive)
+ val repo =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1.subscriptionId]!!
- // SUB_1 is removed from the list...
- interactor.filteredSubscriptions.value = listOf(SUB_2)
+ repo.dataConnectionState.setValue(DataConnectionState.Connected)
- // scope1 is canceled
- assertFalse(scope1!!.isActive)
- assertTrue(scope2!!.isActive)
- }
+ repo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ )
+ assertThat(latest).isEqualTo(true)
+
+ mobileConnectionsRepositoryKairos.fake.defaultMobileIconGroup.setValue(
+ TelephonyIcons.UNKNOWN
+ )
+
+ repo.resolvedNetworkType.setValue(UnknownNetworkType)
+ assertThat(latest).isEqualTo(false)
+
+ repo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileConnectionsRepositoryKairos.fake.LTE_KEY)
+ )
+ assertThat(latest).isEqualTo(true)
+ }
@Test
- fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+ fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubNotShowingRat_false() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
- interactor.filteredSubscriptions.value = emptyList()
+ mobileConnectionsRepositoryKairos.fake.defaultMobileIconGroup.setValue(
+ TelephonyIcons.UNKNOWN
+ )
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
- assertThat(latest).isFalse()
+ val repo1 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1.subscriptionId]!!
- job.cancel()
- }
+ repo1.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ )
+
+ val repo2 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_2.subscriptionId]!!
+
+ repo2.resolvedNetworkType.setValue(UnknownNetworkType)
+
+ assertThat(latest).isFalse()
+ }
@Test
- fun firstMobileSubShowingNetworkTypeIcon_oneSub_notShowingRat_false() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+ fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubShowingRat_true() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
- interactor.filteredSubscriptions.value = listOf(SUB_1)
- // The unknown icon group doesn't show a RAT
- interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ mobileConnectionsRepositoryKairos.fake.defaultMobileIconGroup.setValue(
+ TelephonyIcons.UNKNOWN
+ )
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
- assertThat(latest).isFalse()
+ val repo1 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1.subscriptionId]!!
- job.cancel()
- }
+ repo1.dataConnectionState.setValue(DataConnectionState.Connected)
+ repo1.resolvedNetworkType.setValue(UnknownNetworkType)
+
+ val repo2 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_2.subscriptionId]!!
+
+ repo2.dataConnectionState.setValue(DataConnectionState.Connected)
+ repo2.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ )
+
+ assertThat(latest).isEqualTo(true)
+ }
@Test
- fun firstMobileSubShowingNetworkTypeIcon_oneSub_showingRat_true() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+ fun firstMobileSubShowingNetworkTypeIcon_subListUpdates_valAlsoUpdates() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
- interactor.filteredSubscriptions.value = listOf(SUB_1)
- // The 3G icon group will show a RAT
- interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ mobileConnectionsRepositoryKairos.fake.defaultMobileIconGroup.setValue(
+ TelephonyIcons.UNKNOWN
+ )
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
- assertThat(latest).isTrue()
+ val repo1 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1.subscriptionId]!!
- job.cancel()
- }
+ repo1.dataConnectionState.setValue(DataConnectionState.Connected)
+ repo1.resolvedNetworkType.setValue(UnknownNetworkType)
- @Test
- fun firstMobileSubShowingNetworkTypeIcon_updatesAsSubUpdates() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+ val repo2 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_2.subscriptionId]!!
- interactor.filteredSubscriptions.value = listOf(SUB_1)
- val sub1Interactor = interactor.getInteractorForSubId(1)!!
+ repo2.dataConnectionState.setValue(DataConnectionState.Connected)
+ repo2.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ )
- sub1Interactor.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
- assertThat(latest).isTrue()
+ assertThat(latest).isEqualTo(true)
- sub1Interactor.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
- assertThat(latest).isFalse()
+ // WHEN the sub list gets new subscriptions where the last subscription is not showing
+ // the network type icon
+ setSubscriptions(listOf(SUB_1, SUB_2, SUB_3))
- sub1Interactor.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.LTE)
- assertThat(latest).isTrue()
+ val repo3 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_3.subscriptionId]!!
- job.cancel()
- }
+ repo3.dataConnectionState.setValue(DataConnectionState.Connected)
+ repo3.resolvedNetworkType.setValue(UnknownNetworkType)
+
+ // THEN the flow updates
+ assertThat(latest).isEqualTo(false)
+ }
@Test
- fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubNotShowingRat_false() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+ fun firstMobileSubShowingNetworkTypeIcon_subListReorders_valAlsoUpdates() = runTest {
+ val latest by underTest.firstMobileSubShowingNetworkTypeIcon.collectLastValue()
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
- interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
- interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ mobileConnectionsRepositoryKairos.fake.defaultMobileIconGroup.setValue(
+ TelephonyIcons.UNKNOWN
+ )
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
- assertThat(latest).isFalse()
+ setSubscriptions(listOf(SUB_1, SUB_2))
+ // Immediately switch the order so that we've created both interactors
+ setSubscriptions(listOf(SUB_2, SUB_1))
- job.cancel()
- }
+ val repos = mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()
+ val repo1 = repos[SUB_1.subscriptionId]!!
+ repo1.dataConnectionState.setValue(DataConnectionState.Connected)
- @Test
- fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubShowingRat_true() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
-
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
- interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
- interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
-
- assertThat(latest).isTrue()
- job.cancel()
- }
+ val repo2 = repos[SUB_2.subscriptionId]!!
+ repo2.dataConnectionState.setValue(DataConnectionState.Connected)
- @Test
- fun firstMobileSubShowingNetworkTypeIcon_subListUpdates_valAlsoUpdates() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
-
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
- interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
- interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
-
- assertThat(latest).isTrue()
-
- // WHEN the sub list gets new subscriptions where the last subscription is not showing
- // the network type icon
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
- interactor.getInteractorForSubId(3)!!.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
-
- // THEN the flow updates
- assertThat(latest).isFalse()
-
- job.cancel()
- }
+ setSubscriptions(listOf(SUB_1, SUB_2))
+ repo1.resolvedNetworkType.setValue(UnknownNetworkType)
+ repo2.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileConnectionsRepositoryKairos.fake.GSM_KEY)
+ )
- @Test
- fun firstMobileSubShowingNetworkTypeIcon_subListReorders_valAlsoUpdates() =
- testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
-
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
- // Immediately switch the order so that we've created both interactors
- interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
- val sub1Interactor = interactor.getInteractorForSubId(1)!!
- val sub2Interactor = interactor.getInteractorForSubId(2)!!
-
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
- sub1Interactor.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
- sub2Interactor.networkTypeIconGroup.value =
- NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
- assertThat(latest).isTrue()
-
- // WHEN sub1 becomes last and sub1 has no network type icon
- interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
-
- // THEN the flow updates
- assertThat(latest).isFalse()
-
- // WHEN sub2 becomes last and sub2 has a network type icon
- interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
-
- // THEN the flow updates
- assertThat(latest).isTrue()
-
- job.cancel()
- }
+ assertThat(latest).isEqualTo(true)
+
+ // WHEN sub1 becomes last and sub1 has no network type icon
+ setSubscriptions(listOf(SUB_2, SUB_1))
+
+ // THEN the flow updates
+ assertThat(latest).isEqualTo(false)
+
+ // WHEN sub2 becomes last and sub2 has a network type icon
+ setSubscriptions(listOf(SUB_1, SUB_2))
+
+ // THEN the flow updates
+ assertThat(latest).isEqualTo(true)
+ }
companion object {
private val SUB_1 =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt
index ce35d9d8610f..75f5cbb041c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt
@@ -21,81 +21,83 @@ import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.runKairosTest
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
-import com.android.systemui.lifecycle.activateIn
import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalKairosApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class StackedMobileIconViewModelKairosTest : SysuiTestCase() {
- private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val testScope = kosmos.testScope
-
- private val Kosmos.underTest: StackedMobileIconViewModelKairos by Fixture {
- stackedMobileIconViewModelKairos
- }
+ private val kosmos =
+ testKosmos().useUnconfinedTestDispatcher().apply {
+ mobileConnectionsRepositoryKairos = fakeMobileConnectionsRepositoryKairos
+ featureFlagsClassic.fake.apply {
+ setDefault(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)
+ }
+ }
- @Before
- fun setUp() {
- kosmos.underTest.activateIn(testScope)
- }
+ private val Kosmos.underTest
+ get() = stackedMobileIconViewModelKairos
@Test
@EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
fun dualSim_filtersOutNonDualConnections() =
- kosmos.runTest {
- fakeMobileIconsInteractor.filteredSubscriptions.value = listOf()
+ kosmos.runKairosTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf())
assertThat(underTest.dualSim).isNull()
- fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
assertThat(underTest.dualSim).isNull()
- fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(SUB_1, SUB_2, SUB_3)
+ )
assertThat(underTest.dualSim).isNull()
- fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
assertThat(underTest.dualSim).isNotNull()
}
@Test
@EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
fun dualSim_filtersOutNonCellularIcons() =
- kosmos.runTest {
- fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1)
+ kosmos.runKairosTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
assertThat(underTest.dualSim).isNull()
- fakeMobileIconsInteractor
- .getInteractorForSubId(SUB_1.subscriptionId)!!
- .signalLevelIcon
- .value =
- SignalIconModel.Satellite(
- level = 0,
- icon = Icon.Resource(res = 0, contentDescription = null),
- )
- fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1.subscriptionId]!!
+ .apply {
+ isNonTerrestrial.setValue(true)
+ satelliteLevel.setValue(0)
+ }
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
assertThat(underTest.dualSim).isNull()
}
@Test
@EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
fun dualSim_tracksActiveSubId() =
- kosmos.runTest {
+ kosmos.runKairosTest {
// Active sub id is null, order is unchanged
- fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
setIconLevel(SUB_1.subscriptionId, 1)
setIconLevel(SUB_2.subscriptionId, 2)
@@ -103,16 +105,21 @@ class StackedMobileIconViewModelKairosTest : SysuiTestCase() {
assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2)
// Active sub is 2, order is swapped
- fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(
+ SUB_2.subscriptionId
+ )
assertThat(underTest.dualSim!!.primary.level).isEqualTo(2)
assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1)
}
- private fun setIconLevel(subId: Int, level: Int) {
- with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) {
- signalLevelIcon.value =
- (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level)
+ private suspend fun KairosTestScope.setIconLevel(subId: Int, level: Int) {
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()[subId]!!.apply {
+ isNonTerrestrial.setValue(false)
+ isInService.setValue(true)
+ inflateSignalStrength.setValue(false)
+ isGsm.setValue(true)
+ primaryLevel.setValue(level)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
index d7bcf88f6941..8a7b698623f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt
@@ -45,8 +45,8 @@ class StackedMobileIconViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
- private val Kosmos.underTest: StackedMobileIconViewModel by Fixture {
- stackedMobileIconViewModel
+ private val Kosmos.underTest: StackedMobileIconViewModelImpl by Fixture {
+ stackedMobileIconViewModelImpl
}
@Before
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index db1977b3ff45..93489e90fb85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosImpl
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModelKairos
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
@@ -94,6 +95,7 @@ import kotlinx.coroutines.flow.Flow
MobileRepositorySwitcherKairos.Module::class,
MobileConnectionsRepositoryKairosImpl.Module::class,
MobileIconsInteractorKairosImpl.Module::class,
+ MobileIconsViewModelKairos.Module::class,
MobileConnectionRepositoryKairosFactoryImpl.Module::class,
MobileConnectionsRepositoryKairosAdapter.Module::class,
MobileIconsInteractorKairosAdapter.Module::class,
@@ -217,6 +219,7 @@ abstract class StatusBarPipelineModule {
fun provideFirstMobileSubShowingNetworkTypeIconProvider(
mobileIconsViewModel: MobileIconsViewModel
): Supplier<Flow<Boolean>> {
+ // TODO: kairos-ify
return Supplier<Flow<Boolean>> {
mobileIconsViewModel.firstMobileSubShowingNetworkTypeIcon
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
index a9399593973b..3d58f84e1f91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
@@ -48,6 +48,8 @@ interface MobileIconInteractorKairos {
/** The table log created for this connection */
val tableLogBuffer: TableLogBuffer
+ val subscriptionId: Int
+
/** The current mobile data activity */
val activity: State<DataActivityModel>
@@ -146,6 +148,9 @@ class MobileIconInteractorKairosImpl(
private val carrierIdOverrides: MobileIconCarrierIdOverrides =
MobileIconCarrierIdOverridesImpl(),
) : MobileIconInteractorKairos, KairosBuilder by kairosBuilder() {
+ override val subscriptionId: Int
+ get() = connectionRepository.subId
+
override val tableLogBuffer: TableLogBuffer
get() = connectionRepository.tableLogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt
index fa9fa4c1366f..32ebe884062d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/StackedMobileBindableIcon.kt
@@ -24,7 +24,7 @@ import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.StackedMobileIconBinder
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModelImpl
import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView
import javax.inject.Inject
@@ -34,7 +34,7 @@ class StackedMobileBindableIcon
constructor(
context: Context,
mobileIconsViewModel: MobileIconsViewModel,
- viewModelFactory: StackedMobileIconViewModel.Factory,
+ viewModelFactory: StackedMobileIconViewModelImpl.Factory,
) : BindableIcon {
override val slot: String =
context.getString(com.android.internal.R.string.status_bar_stacked_mobile)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt
index c9fc53ecadc0..fef5bfe2b7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/StackedMobileIconBinder.kt
@@ -25,7 +25,7 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModelImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
import com.android.systemui.statusbar.pipeline.shared.ui.composable.StackedMobileIcon
import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarComposeIconView
@@ -34,7 +34,7 @@ object StackedMobileIconBinder {
fun bind(
view: SingleBindableStatusBarComposeIconView,
mobileIconsViewModel: MobileIconsViewModel,
- viewModelFactory: StackedMobileIconViewModel.Factory,
+ viewModelFactory: StackedMobileIconViewModelImpl.Factory,
): ModernStatusBarViewBinding {
return SingleBindableStatusBarComposeIconView.withDefaultBinding(
view = view,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt
index fce8c85338f3..d2e3a1489040 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -17,14 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import android.graphics.Color
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairos
import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
/**
* A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
@@ -35,45 +33,47 @@ import kotlinx.coroutines.flow.stateIn
* @property location the [StatusBarLocation] of this VM.
* @property verboseLogger an optional logger to log extremely verbose view updates.
*/
+@ExperimentalKairosApi
abstract class LocationBasedMobileViewModelKairos(
- val commonImpl: MobileIconViewModelCommonKairos,
+ val commonImpl: MobileIconViewModelKairosCommon,
val location: StatusBarLocation,
val verboseLogger: VerboseMobileViewLogger?,
-) : MobileIconViewModelCommonKairos by commonImpl {
+) : MobileIconViewModelKairosCommon by commonImpl {
val defaultColor: Int = Color.WHITE
companion object {
fun viewModelForLocation(
- commonImpl: MobileIconViewModelCommon,
- interactor: MobileIconInteractor,
+ commonImpl: MobileIconViewModelKairosCommon,
+ interactor: MobileIconInteractorKairos,
verboseMobileViewLogger: VerboseMobileViewLogger,
location: StatusBarLocation,
- scope: CoroutineScope,
- ): LocationBasedMobileViewModel =
+ ): LocationBasedMobileViewModelKairos =
when (location) {
StatusBarLocation.HOME ->
- HomeMobileIconViewModel(commonImpl, verboseMobileViewLogger)
- StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl)
- StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl)
+ HomeMobileIconViewModelKairos(commonImpl, verboseMobileViewLogger)
+ StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModelKairos(commonImpl)
+ StatusBarLocation.QS -> QsMobileIconViewModelKairos(commonImpl)
StatusBarLocation.SHADE_CARRIER_GROUP ->
- ShadeCarrierGroupMobileIconViewModel(commonImpl, interactor, scope)
+ ShadeCarrierGroupMobileIconViewModelKairos(commonImpl, interactor)
}
}
}
+@ExperimentalKairosApi
class HomeMobileIconViewModelKairos(
- commonImpl: MobileIconViewModelCommonKairos,
+ commonImpl: MobileIconViewModelKairosCommon,
verboseMobileViewLogger: VerboseMobileViewLogger,
) :
- MobileIconViewModelCommonKairos,
+ MobileIconViewModelKairosCommon,
LocationBasedMobileViewModelKairos(
commonImpl,
location = StatusBarLocation.HOME,
verboseMobileViewLogger,
)
-class QsMobileIconViewModelKairos(commonImpl: MobileIconViewModelCommonKairos) :
- MobileIconViewModelCommonKairos,
+@ExperimentalKairosApi
+class QsMobileIconViewModelKairos(commonImpl: MobileIconViewModelKairosCommon) :
+ MobileIconViewModelKairosCommon,
LocationBasedMobileViewModelKairos(
commonImpl,
location = StatusBarLocation.QS,
@@ -81,30 +81,34 @@ class QsMobileIconViewModelKairos(commonImpl: MobileIconViewModelCommonKairos) :
verboseLogger = null,
)
+@ExperimentalKairosApi
class ShadeCarrierGroupMobileIconViewModelKairos(
- commonImpl: MobileIconViewModelCommonKairos,
- interactor: MobileIconInteractor,
- scope: CoroutineScope,
+ commonImpl: MobileIconViewModelKairosCommon,
+ private val interactor: MobileIconInteractorKairos,
) :
- MobileIconViewModelCommonKairos,
+ MobileIconViewModelKairosCommon,
LocationBasedMobileViewModelKairos(
commonImpl,
location = StatusBarLocation.SHADE_CARRIER_GROUP,
// Only do verbose logging for the Home location.
verboseLogger = null,
) {
- private val isSingleCarrier = interactor.isSingleCarrier
- val carrierName = interactor.carrierName
- override val isVisible: StateFlow<Boolean> =
+ private val isSingleCarrier: State<Boolean>
+ get() = interactor.isSingleCarrier
+
+ val carrierName: State<String>
+ get() = interactor.carrierName
+
+ override val isVisible: State<Boolean> =
combine(super.isVisible, isSingleCarrier) { isVisible, isSingleCarrier ->
- if (isSingleCarrier) false else isVisible
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), super.isVisible.value)
+ !isSingleCarrier && isVisible
+ }
}
-class KeyguardMobileIconViewModelKairos(commonImpl: MobileIconViewModelCommonKairos) :
- MobileIconViewModelCommonKairos,
+@ExperimentalKairosApi
+class KeyguardMobileIconViewModelKairos(commonImpl: MobileIconViewModelKairosCommon) :
+ MobileIconViewModelKairosCommon,
LocationBasedMobileViewModelKairos(
commonImpl,
location = StatusBarLocation.KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt
index cc7fc0964dae..0a0f9640a920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -17,206 +17,192 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import com.android.systemui.Flags.statusBarStaticInoutIndicators
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State as KairosState
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.res.R
import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairos
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */
-interface MobileIconViewModelCommonKairos : MobileIconViewModelCommon {
- override val subscriptionId: Int
+@ExperimentalKairosApi
+interface MobileIconViewModelKairosCommon {
+ val subscriptionId: Int
+ val iconInteractor: MobileIconInteractorKairos
/** True if this view should be visible at all. */
- override val isVisible: StateFlow<Boolean>
- override val icon: Flow<SignalIconModel>
- override val contentDescription: Flow<MobileContentDescription?>
- override val roaming: Flow<Boolean>
+ val isVisible: KairosState<Boolean>
+ val icon: KairosState<SignalIconModel>
+ val contentDescription: KairosState<MobileContentDescription?>
+ val roaming: KairosState<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- override val networkTypeIcon: Flow<Icon.Resource?>
+ val networkTypeIcon: KairosState<Icon.Resource?>
/** The slice attribution. Drawn as a background layer */
- override val networkTypeBackground: StateFlow<Icon.Resource?>
- override val activityInVisible: Flow<Boolean>
- override val activityOutVisible: Flow<Boolean>
- override val activityContainerVisible: Flow<Boolean>
+ val networkTypeBackground: KairosState<Icon.Resource?>
+ val activityInVisible: KairosState<Boolean>
+ val activityOutVisible: KairosState<Boolean>
+ val activityContainerVisible: KairosState<Boolean>
}
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
- * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * a single line of service via [MobileIconInteractorKairos] and update the UI based on that
* subscription's information.
*
- * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
- * [MobileIconsInteractor.filteredSubscriptions].
+ * There will be exactly one [MobileIconViewModelKairos] per filtered subscription offered from
+ * [MobileIconsInteractorKairos.filteredSubscriptions].
*
- * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
- * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
- * model gets the exact same information, as well as allows us to log that unified state only once
- * per icon.
+ * For the sake of keeping log spam in check, every flow funding the
+ * [MobileIconViewModelKairosCommon] interface is implemented as a [StateFlow]. This ensures that
+ * each location-based mobile icon view model gets the exact same information, as well as allows us
+ * to log that unified state only once per icon.
*/
+@ExperimentalKairosApi
class MobileIconViewModelKairos(
override val subscriptionId: Int,
- iconInteractor: MobileIconInteractor,
- airplaneModeInteractor: AirplaneModeInteractor,
- constants: ConnectivityConstants,
- scope: CoroutineScope,
-) : MobileIconViewModelCommonKairos {
- private val cellProvider by lazy {
- CellularIconViewModelKairos(
- subscriptionId,
- iconInteractor,
- airplaneModeInteractor,
- constants,
- scope,
- )
+ override val iconInteractor: MobileIconInteractorKairos,
+ private val airplaneModeInteractor: AirplaneModeInteractor,
+ private val constants: ConnectivityConstants,
+ private val flags: FeatureFlagsClassic,
+) : MobileIconViewModelKairosCommon, KairosBuilder by kairosBuilder() {
+
+ private val isAirplaneMode: State<Boolean> = buildState {
+ airplaneModeInteractor.isAirplaneMode.toState()
}
private val satelliteProvider by lazy {
- CarrierBasedSatelliteViewModelKairosImpl(
- subscriptionId,
- airplaneModeInteractor,
- iconInteractor,
- scope,
- )
+ CarrierBasedSatelliteViewModelKairosImpl(subscriptionId, iconInteractor, isAirplaneMode)
}
/**
* Similar to repository switching, this allows us to split up the logic of satellite/cellular
* states, since they are different by nature
*/
- private val vmProvider: Flow<MobileIconViewModelCommon> =
- iconInteractor.isNonTerrestrial
- .mapLatest { nonTerrestrial ->
- if (nonTerrestrial) {
- satelliteProvider
- } else {
- cellProvider
+ private val vmProvider: KairosState<MobileIconViewModelKairosCommon> = buildState {
+ iconInteractor.isNonTerrestrial.mapLatestBuild { nonTerrestrial ->
+ if (nonTerrestrial) {
+ satelliteProvider
+ } else {
+ activated {
+ CellularIconViewModelKairos(
+ subscriptionId,
+ iconInteractor,
+ airplaneModeInteractor,
+ constants,
+ flags,
+ )
}
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider)
+ }
+ }
- override val isVisible: StateFlow<Boolean> =
- vmProvider
- .flatMapLatest { it.isVisible }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isVisible: KairosState<Boolean> = vmProvider.flatMap { it.isVisible }
- override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }
+ override val icon: KairosState<SignalIconModel> = vmProvider.flatMap { it.icon }
- override val contentDescription: Flow<MobileContentDescription?> =
- vmProvider.flatMapLatest { it.contentDescription }
+ override val contentDescription: KairosState<MobileContentDescription?> =
+ vmProvider.flatMap { it.contentDescription }
- override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }
+ override val roaming: KairosState<Boolean> = vmProvider.flatMap { it.roaming }
- override val networkTypeIcon: Flow<Icon.Resource?> =
- vmProvider.flatMapLatest { it.networkTypeIcon }
+ override val networkTypeIcon: KairosState<Icon.Resource?> =
+ vmProvider.flatMap { it.networkTypeIcon }
- override val networkTypeBackground: StateFlow<Icon.Resource?> =
- vmProvider
- .flatMapLatest { it.networkTypeBackground }
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ override val networkTypeBackground: KairosState<Icon.Resource?> =
+ vmProvider.flatMap { it.networkTypeBackground }
- override val activityInVisible: Flow<Boolean> =
- vmProvider.flatMapLatest { it.activityInVisible }
+ override val activityInVisible: KairosState<Boolean> =
+ vmProvider.flatMap { it.activityInVisible }
- override val activityOutVisible: Flow<Boolean> =
- vmProvider.flatMapLatest { it.activityOutVisible }
+ override val activityOutVisible: KairosState<Boolean> =
+ vmProvider.flatMap { it.activityOutVisible }
- override val activityContainerVisible: Flow<Boolean> =
- vmProvider.flatMapLatest { it.activityContainerVisible }
+ override val activityContainerVisible: KairosState<Boolean> =
+ vmProvider.flatMap { it.activityContainerVisible }
}
/** Representation of this network when it is non-terrestrial (e.g., satellite) */
+@ExperimentalKairosApi
private class CarrierBasedSatelliteViewModelKairosImpl(
override val subscriptionId: Int,
- airplaneModeInteractor: AirplaneModeInteractor,
- interactor: MobileIconInteractor,
- scope: CoroutineScope,
-) : MobileIconViewModelCommon, MobileIconViewModelCommonKairos {
- override val isVisible: StateFlow<Boolean> =
- airplaneModeInteractor.isAirplaneMode
- .map { !it }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon
+ override val iconInteractor: MobileIconInteractorKairos,
+ isAirplaneMode: KairosState<Boolean>,
+) : MobileIconViewModelKairosCommon {
+ override val isVisible: KairosState<Boolean> = isAirplaneMode.map { !it }
+ override val icon: KairosState<SignalIconModel>
+ get() = iconInteractor.signalLevelIcon
- override val contentDescription: Flow<MobileContentDescription?> = MutableStateFlow(null)
+ override val contentDescription: KairosState<MobileContentDescription?> = stateOf(null)
/** These fields are not used for satellite icons currently */
- override val roaming: Flow<Boolean> = flowOf(false)
- override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null)
- override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null)
- override val activityInVisible: Flow<Boolean> = flowOf(false)
- override val activityOutVisible: Flow<Boolean> = flowOf(false)
- override val activityContainerVisible: Flow<Boolean> = flowOf(false)
+ override val roaming: KairosState<Boolean> = stateOf(false)
+ override val networkTypeIcon: KairosState<Icon.Resource?> = stateOf(null)
+ override val networkTypeBackground: KairosState<Icon.Resource?> = stateOf(null)
+ override val activityInVisible: KairosState<Boolean> = stateOf(false)
+ override val activityOutVisible: KairosState<Boolean> = stateOf(false)
+ override val activityContainerVisible: KairosState<Boolean> = stateOf(false)
}
/** Terrestrial (cellular) icon. */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@ExperimentalKairosApi
private class CellularIconViewModelKairos(
override val subscriptionId: Int,
- iconInteractor: MobileIconInteractor,
+ override val iconInteractor: MobileIconInteractorKairos,
airplaneModeInteractor: AirplaneModeInteractor,
constants: ConnectivityConstants,
- scope: CoroutineScope,
-) : MobileIconViewModelCommon, MobileIconViewModelCommonKairos {
- override val isVisible: StateFlow<Boolean> =
+ flags: FeatureFlagsClassic,
+) : MobileIconViewModelKairosCommon, KairosBuilder by kairosBuilder() {
+
+ override val isVisible: KairosState<Boolean> =
if (!constants.hasDataCapabilities) {
- flowOf(false)
- } else {
+ stateOf(false)
+ } else {
+ buildState {
combine(
- airplaneModeInteractor.isAirplaneMode,
- iconInteractor.isAllowedDuringAirplaneMode,
- iconInteractor.isForceHidden,
- ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden ->
- if (isForceHidden) {
- false
- } else if (isAirplaneMode) {
- isAllowedDuringAirplaneMode
- } else {
- true
+ airplaneModeInteractor.isAirplaneMode.toState(),
+ iconInteractor.isAllowedDuringAirplaneMode,
+ iconInteractor.isForceHidden,
+ ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden ->
+ if (isForceHidden) {
+ false
+ } else if (isAirplaneMode) {
+ isAllowedDuringAirplaneMode
+ } else {
+ true
+ }
+ }
+ .also {
+ logDiffsForTable(it, iconInteractor.tableLogBuffer, columnName = "visible")
}
- }
}
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnName = "visible",
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ }
- override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon
+ override val icon: KairosState<SignalIconModel>
+ get() = iconInteractor.signalLevelIcon
- override val contentDescription: Flow<MobileContentDescription?> =
+ override val contentDescription: KairosState<MobileContentDescription?> =
combine(iconInteractor.signalLevelIcon, iconInteractor.networkName) { icon, nameModel ->
- when (icon) {
- is SignalIconModel.Cellular ->
- MobileContentDescription.Cellular(
- nameModel.name,
- icon.levelDescriptionRes(),
- )
- else -> null
- }
+ when (icon) {
+ is SignalIconModel.Cellular ->
+ MobileContentDescription.Cellular(nameModel.name, icon.levelDescriptionRes())
+ else -> null
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ }
private fun SignalIconModel.Cellular.levelDescriptionRes() =
when (level) {
@@ -241,7 +227,7 @@ private class CellularIconViewModelKairos(
else -> R.string.accessibility_no_signal
}
- private val showNetworkTypeIcon: Flow<Boolean> =
+ private val showNetworkTypeIcon: KairosState<Boolean> =
combine(
iconInteractor.isDataConnected,
iconInteractor.isDataEnabled,
@@ -252,77 +238,72 @@ private class CellularIconViewModelKairos(
alwaysShow ||
(!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault))
}
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnName = "showNetworkTypeIcon",
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- override val networkTypeIcon: Flow<Icon.Resource?> =
- combine(iconInteractor.networkTypeIconGroup, showNetworkTypeIcon) {
- networkTypeIconGroup,
- shouldShow ->
- val desc =
- if (networkTypeIconGroup.contentDescription != 0)
- ContentDescription.Resource(networkTypeIconGroup.contentDescription)
- else null
- val icon =
- if (networkTypeIconGroup.iconId != 0)
- Icon.Resource(networkTypeIconGroup.iconId, desc)
- else null
- return@combine when {
- !shouldShow -> null
- else -> icon
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ iconInteractor.tableLogBuffer,
+ columnName = "showNetworkTypeIcon",
+ )
}
}
- .distinctUntilChanged()
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
-
- override val networkTypeBackground =
- iconInteractor.showSliceAttribution
- .map {
- when {
- it && NewStatusBarIcons.isEnabled ->
- Icon.Resource(R.drawable.mobile_network_type_background_updated, null)
- it -> Icon.Resource(R.drawable.mobile_network_type_background, null)
- else -> null
+
+ override val networkTypeIcon: KairosState<Icon.Resource?> =
+ combine(iconInteractor.networkTypeIconGroup, showNetworkTypeIcon) {
+ networkTypeIconGroup,
+ shouldShow ->
+ val desc =
+ if (networkTypeIconGroup.contentDescription != 0) {
+ ContentDescription.Resource(networkTypeIconGroup.contentDescription)
+ } else {
+ null
+ }
+ val icon =
+ if (networkTypeIconGroup.iconId != 0) {
+ Icon.Resource(networkTypeIconGroup.iconId, desc)
+ } else {
+ null
}
+ when {
+ !shouldShow -> null
+ else -> icon
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
-
- override val roaming: StateFlow<Boolean> =
- iconInteractor.isRoaming
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnName = "roaming",
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- private val activity: Flow<DataActivityModel?> =
+ }
+
+ override val networkTypeBackground: KairosState<Icon.Resource?> =
+ iconInteractor.showSliceAttribution.map {
+ when {
+ it && NewStatusBarIcons.isEnabled ->
+ Icon.Resource(R.drawable.mobile_network_type_background_updated, null)
+ it -> Icon.Resource(R.drawable.mobile_network_type_background, null)
+ else -> null
+ }
+ }
+
+ override val roaming: KairosState<Boolean> =
+ iconInteractor.isRoaming.also {
+ onActivated {
+ logDiffsForTable(it, iconInteractor.tableLogBuffer, columnName = "roaming")
+ }
+ }
+
+ private val activity: KairosState<DataActivityModel?> =
if (!constants.shouldShowActivityConfig) {
- flowOf(null)
+ stateOf(null)
} else {
iconInteractor.activity
}
- override val activityInVisible: Flow<Boolean> =
- activity
- .map { it?.hasActivityIn ?: false }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val activityInVisible: KairosState<Boolean> =
+ activity.map { it?.hasActivityIn ?: false }
- override val activityOutVisible: Flow<Boolean> =
- activity
- .map { it?.hasActivityOut ?: false }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val activityOutVisible: KairosState<Boolean> =
+ activity.map { it?.hasActivityOut ?: false }
- override val activityContainerVisible: Flow<Boolean> =
+ override val activityContainerVisible: KairosState<Boolean> =
if (statusBarStaticInoutIndicators()) {
- flowOf(constants.shouldShowActivityConfig)
- } else {
- activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ stateOf(constants.shouldShowActivityConfig)
+ } else {
+ activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt
index a65540738828..ada5500a6f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -16,137 +16,149 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
-import androidx.annotation.VisibleForTesting
-import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.coroutines.newTracingContext
+import androidx.compose.runtime.State as ComposeState
+import androidx.compose.runtime.mutableStateOf
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.BuildSpec
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State as KairosState
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.changes
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.flatten
+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.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairos
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairos
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import java.util.concurrent.ConcurrentHashMap
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
+import javax.inject.Provider
/**
* View model for describing the system's current mobile cellular connections. The result is a list
* of [MobileIconViewModel]s which describe the individual icons and can be bound to
* [ModernStatusBarMobileView].
*/
+@ExperimentalKairosApi
@SysUISingleton
class MobileIconsViewModelKairos
@Inject
constructor(
val logger: MobileViewLogger,
private val verboseLogger: VerboseMobileViewLogger,
- private val interactor: MobileIconsInteractor,
+ private val interactor: MobileIconsInteractorKairos,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
- @Background private val scope: CoroutineScope,
-) {
- @VisibleForTesting
- val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>()
+ private val flags: FeatureFlagsClassic,
+) : KairosBuilder by kairosBuilder() {
- val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId
+ val activeSubscriptionId: State<Int?>
+ get() = interactor.activeDataIconInteractor.map { it?.subscriptionId }
- val subscriptionIdsFlow: StateFlow<List<Int>> =
- interactor.filteredSubscriptions
- .mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
-
- val mobileSubViewModels: StateFlow<List<MobileIconViewModelCommon>> =
- subscriptionIdsFlow
- .map { ids -> ids.map { commonViewModelForSub(it) } }
- .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+ val subscriptionIds: KairosState<List<Int>> =
+ interactor.filteredSubscriptions.map { subscriptions ->
+ subscriptions.map { it.subscriptionId }
+ }
- private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> =
- mobileSubViewModels
- .map {
- if (it.isEmpty()) {
- null
- } else {
- // Mobile icons get reversed by [StatusBarIconController], so the last element
- // in this list will show up visually first.
- it.last()
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ val icons: Incremental<Int, MobileIconViewModelKairos> = buildIncremental {
+ interactor.icons
+ .mapValues { (subId, icon) -> buildSpec { commonViewModel(subId, icon) } }
+ .applyLatestSpecForKey()
+ }
- /**
- * A flow that emits `true` if the mobile sub that's displayed first visually is showing its
- * network type icon and `false` otherwise.
- */
- val firstMobileSubShowingNetworkTypeIcon: StateFlow<Boolean> =
- firstMobileSubViewModel
- .flatMapLatest { firstMobileSubViewModel ->
- firstMobileSubViewModel?.networkTypeIcon?.map { it != null } ?: flowOf(false)
+ /** Whether the mobile sub that's displayed first visually is showing its network type icon. */
+ val firstMobileSubShowingNetworkTypeIcon: KairosState<Boolean> = buildState {
+ combine(subscriptionIds.map { it.lastOrNull() }, icons) { lastId, icons ->
+ icons[lastId]?.networkTypeIcon?.map { it != null } ?: stateOf(false)
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- val isStackable: StateFlow<Boolean> = interactor.isStackable
-
- init {
- scope.launch { subscriptionIdsFlow.collect { invalidateCaches(it) } }
+ .flatten()
}
- fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
- val common = commonViewModelForSub(subId)
- return LocationBasedMobileViewModel.viewModelForLocation(
- common,
- interactor.getMobileConnectionInteractorForSubId(subId),
+ val isStackable: KairosState<Boolean>
+ get() = interactor.isStackable
+
+ fun viewModelForSub(
+ subId: Int,
+ location: StatusBarLocation,
+ ): BuildSpec<LocationBasedMobileViewModelKairos> = buildSpec {
+ val iconInteractor =
+ interactor.icons.sample().getOrElse(subId) { error("Unknown subscription id: $subId") }
+ val commonViewModel =
+ icons.sample().getOrElse(subId) { error("Unknown subscription id: $subId") }
+ LocationBasedMobileViewModelKairos.viewModelForLocation(
+ commonViewModel,
+ iconInteractor,
verboseLogger,
location,
- scope,
)
}
- private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon {
- return reuseCache.getOrPut(subId) { createViewModel(subId) }.first
- }
-
- private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> {
- // Create a child scope so we can cancel it
- val vmScope = scope.createChildScope(newTracingContext("MobileIconViewModel"))
- val vm =
- MobileIconViewModel(
- subId,
- interactor.getMobileConnectionInteractorForSubId(subId),
- airplaneModeInteractor,
- constants,
- vmScope,
+ fun shadeCarrierGroupIcon(subId: Int): BuildSpec<ShadeCarrierGroupMobileIconViewModelKairos> =
+ buildSpec {
+ val iconInteractor =
+ interactor.icons.sample().getOrElse(subId) {
+ error("Unknown subscription id: $subId")
+ }
+ val commonViewModel =
+ icons.sample().getOrElse(subId) { error("Unknown subscription id: $subId") }
+ ShadeCarrierGroupMobileIconViewModelKairos(commonViewModel, iconInteractor)
+ }
+
+ private fun BuildScope.commonViewModel(subId: Int, iconInteractor: MobileIconInteractorKairos) =
+ activated {
+ MobileIconViewModelKairos(
+ subscriptionId = subId,
+ iconInteractor = iconInteractor,
+ airplaneModeInteractor = airplaneModeInteractor,
+ constants = constants,
+ flags = flags,
)
-
- return Pair(vm, vmScope)
+ }
+
+ @dagger.Module
+ object Module {
+ @Provides
+ @ElementsIntoSet
+ fun bindKairosActivatable(
+ impl: Provider<MobileIconsViewModelKairos>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
}
+}
- private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) =
- CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext)
+@ExperimentalKairosApi
+class MobileIconsViewModelKairosComposeWrapper(
+ val icons: ComposeState<Map<Int, MobileIconViewModelKairos>>
+)
- private fun invalidateCaches(subIds: List<Int>) {
- reuseCache.keys
- .filter { !subIds.contains(it) }
- .forEach { id ->
- reuseCache
- .remove(id)
- // Cancel the view model's scope after removing it
- ?.second
- ?.cancel()
- }
- }
+@ExperimentalKairosApi
+fun composeWrapper(
+ viewModel: MobileIconsViewModelKairos
+): BuildSpec<MobileIconsViewModelKairosComposeWrapper> = buildSpec {
+ MobileIconsViewModelKairosComposeWrapper(icons = toComposeState(viewModel.icons))
+}
+
+@ExperimentalKairosApi
+fun <T> BuildScope.toComposeState(state: KairosState<T>): ComposeState<T> {
+ val initial = state.sample()
+ val cState = mutableStateOf(initial)
+ state.changes.observe { cState.value = it }
+ return cState
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
index 2c85a5150575..060454c2b1c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt
@@ -22,6 +22,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel.DualSim
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,10 +31,22 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+interface StackedMobileIconViewModel {
+ val dualSim: DualSim?
+ val networkTypeIcon: Icon.Resource?
+ val isIconVisible: Boolean
+
+ data class DualSim(
+ val primary: SignalIconModel.Cellular,
+ val secondary: SignalIconModel.Cellular,
+ )
+}
+
@OptIn(ExperimentalCoroutinesApi::class)
-class StackedMobileIconViewModel
+class StackedMobileIconViewModelImpl
@AssistedInject
-constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable() {
+constructor(mobileIconsViewModel: MobileIconsViewModel) :
+ ExclusiveActivatable(), StackedMobileIconViewModel {
private val hydrator = Hydrator("StackedMobileIconViewModel")
private val isStackable: Boolean by
@@ -52,7 +65,7 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable()
viewModels.sortedByDescending { it.subscriptionId == activeSubId }
}
- val dualSim: DualSim? by
+ override val dualSim: DualSim? by
hydrator.hydratedStateOf(
traceName = "dualSim",
source =
@@ -68,7 +81,7 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable()
initialValue = null,
)
- val networkTypeIcon: Icon.Resource? by
+ override val networkTypeIcon: Icon.Resource? by
hydrator.hydratedStateOf(
traceName = "networkTypeIcon",
source =
@@ -78,7 +91,7 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable()
initialValue = null,
)
- val isIconVisible: Boolean by derivedStateOf { isStackable && dualSim != null }
+ override val isIconVisible: Boolean by derivedStateOf { isStackable && dualSim != null }
override suspend fun onActivated(): Nothing {
hydrator.activate()
@@ -86,11 +99,6 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable()
@AssistedFactory
interface Factory {
- fun create(): StackedMobileIconViewModel
+ fun create(): StackedMobileIconViewModelImpl
}
-
- data class DualSim(
- val primary: SignalIconModel.Cellular,
- val secondary: SignalIconModel.Cellular,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt
index 2dbb02c8f095..402fdf03941d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt
@@ -16,81 +16,71 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import com.android.systemui.KairosBuilder
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State as KairosState
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.StackedMobileIconViewModel.DualSim
+import com.android.systemui.util.composable.kairos.hydratedComposeStateOf
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-@OptIn(ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalKairosApi::class)
class StackedMobileIconViewModelKairos
@AssistedInject
-constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable() {
- private val hydrator = Hydrator("StackedMobileIconViewModel")
+constructor(mobileIcons: MobileIconsViewModelKairos) :
+ KairosBuilder by kairosBuilder(), StackedMobileIconViewModel {
private val isStackable: Boolean by
- hydrator.hydratedStateOf(
- traceName = "isStackable",
- source = mobileIconsViewModel.isStackable,
- initialValue = false,
- )
+ hydratedComposeStateOf(mobileIcons.isStackable, initialValue = false)
- private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> =
- combine(
- mobileIconsViewModel.mobileSubViewModels,
- mobileIconsViewModel.activeMobileDataSubscriptionId,
- ) { viewModels, activeSubId ->
- // Sort to get the active subscription first, if it's set
- viewModels.sortedByDescending { it.subscriptionId == activeSubId }
+ private val iconList: KairosState<List<MobileIconViewModelKairos>> =
+ combine(mobileIcons.icons, mobileIcons.activeSubscriptionId) { iconsBySubId, activeSubId ->
+ buildList {
+ activeSubId?.let { iconsBySubId[activeSubId]?.let { add(it) } }
+ addAll(iconsBySubId.values.asSequence().filter { it.subscriptionId != activeSubId })
+ }
}
- val dualSim: DualSim? by
- hydrator.hydratedStateOf(
- traceName = "dualSim",
- source =
- iconViewModelFlow.flatMapLatest { viewModels ->
- combine(viewModels.map { it.icon }) { icons ->
- icons
- .toList()
- .filterIsInstance<SignalIconModel.Cellular>()
- .takeIf { it.size == 2 }
- ?.let { DualSim(it[0], it[1]) }
- }
- },
+ override val dualSim: DualSim? by
+ hydratedComposeStateOf(
+ iconList.flatMap { icons ->
+ icons.map { it.icon }.combine { signalIcons -> tryParseDualSim(signalIcons) }
+ },
initialValue = null,
)
- val networkTypeIcon: Icon.Resource? by
- hydrator.hydratedStateOf(
- traceName = "networkTypeIcon",
- source =
- iconViewModelFlow.flatMapLatest { viewModels ->
- viewModels.firstOrNull()?.networkTypeIcon ?: flowOf(null)
- },
+ override val networkTypeIcon: Icon.Resource? by
+ hydratedComposeStateOf(
+ iconList.flatMap { icons -> icons.firstOrNull()?.networkTypeIcon ?: stateOf(null) },
initialValue = null,
)
- val isIconVisible: Boolean by derivedStateOf { isStackable && dualSim != null }
+ override val isIconVisible: Boolean
+ get() = isStackable && dualSim != null
- override suspend fun onActivated(): Nothing {
- hydrator.activate()
+ private fun tryParseDualSim(icons: List<SignalIconModel>): DualSim? {
+ var first: SignalIconModel.Cellular? = null
+ var second: SignalIconModel.Cellular? = null
+ for (icon in icons) {
+ when {
+ icon !is SignalIconModel.Cellular -> continue
+ first == null -> first = icon
+ second == null -> second = icon
+ else -> return null
+ }
+ }
+ return first?.let { second?.let { DualSim(first, second) } }
}
@AssistedFactory
interface Factory {
fun create(): StackedMobileIconViewModelKairos
}
-
- data class DualSim(
- val primary: SignalIconModel.Cellular,
- val secondary: SignalIconModel.Cellular,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/composable/kairos/HydratedComposeStateOf.kt b/packages/SystemUI/src/com/android/systemui/util/composable/kairos/HydratedComposeStateOf.kt
new file mode 100644
index 000000000000..0d53a001f3f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/composable/kairos/HydratedComposeStateOf.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.util.composable.kairos
+
+import androidx.compose.runtime.mutableStateOf
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+
+@ExperimentalKairosApi
+fun <T> KairosBuilder.hydratedComposeStateOf(
+ source: State<T>,
+ initialValue: T,
+): androidx.compose.runtime.State<T> =
+ mutableStateOf(initialValue).also { state ->
+ onActivated { source.observe { state.value = it } }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosKosmos.kt
new file mode 100644
index 000000000000..83b8283b1892
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractorKairos
+import com.android.systemui.statusbar.pipeline.mobile.ui.mobileViewLogger
+import com.android.systemui.util.mockito.mock
+
+@ExperimentalKairosApi
+val Kosmos.mobileIconsViewModelKairos by ActivatedKairosFixture {
+ MobileIconsViewModelKairos(
+ logger = mobileViewLogger,
+ verboseLogger = mock(),
+ interactor = mobileIconsInteractorKairos,
+ airplaneModeInteractor = airplaneModeInteractor,
+ constants = mock(),
+ flags = featureFlagsClassic,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt
index 3ee33802e9d5..ad42a89dc237 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt
@@ -16,7 +16,11 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
import com.android.systemui.kosmos.Kosmos
-val Kosmos.stackedMobileIconViewModelKairos by
- Kosmos.Fixture { StackedMobileIconViewModelKairos(mobileIconsViewModel) }
+@ExperimentalKairosApi
+val Kosmos.stackedMobileIconViewModelKairos by ActivatedKairosFixture {
+ StackedMobileIconViewModelKairos(mobileIconsViewModelKairos)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
index 880ba5eee5d2..0a8e0a7d48b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt
@@ -18,5 +18,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-val Kosmos.stackedMobileIconViewModel: StackedMobileIconViewModel by
- Kosmos.Fixture { StackedMobileIconViewModel(mobileIconsViewModel) }
+var Kosmos.stackedMobileIconViewModel: StackedMobileIconViewModel by
+ Kosmos.Fixture { stackedMobileIconViewModelImpl }
+
+val Kosmos.stackedMobileIconViewModelImpl by
+ Kosmos.Fixture { StackedMobileIconViewModelImpl(mobileIconsViewModel) }