diff options
12 files changed, 285 insertions, 45 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt new file mode 100644 index 000000000000..da87f7306e60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.model + +import android.telephony.TelephonyManager.DATA_CONNECTED +import android.telephony.TelephonyManager.DATA_CONNECTING +import android.telephony.TelephonyManager.DATA_DISCONNECTED +import android.telephony.TelephonyManager.DATA_DISCONNECTING +import android.telephony.TelephonyManager.DataState + +/** Internal enum representation of the telephony data connection states */ +enum class DataConnectionState(@DataState val dataState: Int) { + Connected(DATA_CONNECTED), + Connecting(DATA_CONNECTING), + Disconnected(DATA_DISCONNECTED), + Disconnecting(DATA_DISCONNECTING), +} + +fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState = + when (this) { + DATA_CONNECTED -> DataConnectionState.Connected + DATA_CONNECTING -> DataConnectionState.Connecting + DATA_DISCONNECTED -> DataConnectionState.Disconnected + DATA_DISCONNECTING -> DataConnectionState.Disconnecting + else -> throw IllegalArgumentException("unknown data state received") + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt index eaba0e93e750..6341a114112c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt @@ -28,6 +28,7 @@ import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected /** * Data class containing all of the relevant information for a particular line of service, known as @@ -49,14 +50,14 @@ data class MobileSubscriptionModel( @IntRange(from = 0, to = 4) val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, - /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */ - val dataConnectionState: Int? = null, + /** Mapped from [DataConnectionStateListener.onDataConnectionStateChanged] */ + val dataConnectionState: DataConnectionState = Disconnected, /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */ @DataActivityType val dataActivityDirection: Int? = null, /** From [CarrierNetworkListener.onCarrierNetworkChange] */ - val carrierNetworkChangeActive: Boolean? = null, + val carrierNetworkChangeActive: Boolean = false, /** * From [DisplayInfoListener.onDisplayInfoChanged]. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 45284cf0332b..06e8f467ee0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -31,7 +31,9 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import java.lang.IllegalStateException import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -42,7 +44,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** @@ -62,13 +64,15 @@ interface MobileConnectionRepository { * listener + model. */ val subscriptionModelFlow: Flow<MobileSubscriptionModel> + /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */ + val dataEnabled: Flow<Boolean> } @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) class MobileConnectionRepositoryImpl( private val subId: Int, - telephonyManager: TelephonyManager, + private val telephonyManager: TelephonyManager, bgDispatcher: CoroutineDispatcher, logger: ConnectivityPipelineLogger, scope: CoroutineScope, @@ -127,7 +131,8 @@ class MobileConnectionRepositoryImpl( dataState: Int, networkType: Int ) { - state = state.copy(dataConnectionState = dataState) + state = + state.copy(dataConnectionState = dataState.toDataConnectionType()) trySend(state) } @@ -160,10 +165,21 @@ class MobileConnectionRepositoryImpl( telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } } - .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) } + .logOutputChange(logger, "MobileSubscriptionModel") .stateIn(scope, SharingStarted.WhileSubscribed(), state) } + /** + * There are a few cases where we will need to poll [TelephonyManager] so we can update some + * internal state where callbacks aren't provided. Any of those events should be merged into + * this flow, which can be used to trigger the polling. + */ + private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {} + + override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() } + + private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed + class Factory @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 15f4acc1127c..f99d278c3903 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -29,6 +29,9 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map interface MobileIconInteractor { + /** Observable for the data enabled state of this connection */ + val isDataEnabled: Flow<Boolean> + /** Observable for RAT type (network type) indicator */ val networkTypeIconGroup: Flow<MobileIconGroup> @@ -54,6 +57,8 @@ class MobileIconInteractorImpl( ) : MobileIconInteractor { private val mobileStatusInfo = connectionRepository.subscriptionModelFlow + override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled + /** Observable for the current RAT indicator icon ([MobileIconGroup]) */ override val networkTypeIconGroup: Flow<MobileIconGroup> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index cd411a4a2afe..614d583c3c48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -23,12 +23,14 @@ import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -47,28 +49,38 @@ import kotlinx.coroutines.flow.stateIn * icon */ interface MobileIconsInteractor { + /** List of subscriptions, potentially filtered for CBRS */ val filteredSubscriptions: Flow<List<SubscriptionInfo>> + /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> + /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ val defaultMobileIconGroup: Flow<MobileIconGroup> + /** True once the user has been set up */ val isUserSetup: Flow<Boolean> + /** + * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given + * subId. Will throw if the ID is invalid + */ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor } +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileIconsInteractorImpl @Inject constructor( - private val mobileSubscriptionRepo: MobileConnectionsRepository, + private val mobileConnectionsRepo: MobileConnectionsRepository, private val carrierConfigTracker: CarrierConfigTracker, private val mobileMappingsProxy: MobileMappingsProxy, userSetupRepo: UserSetupRepository, @Application private val scope: CoroutineScope, ) : MobileIconsInteractor { private val activeMobileDataSubscriptionId = - mobileSubscriptionRepo.activeMobileDataSubscriptionId + mobileConnectionsRepo.activeMobileDataSubscriptionId private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> = - mobileSubscriptionRepo.subscriptionsFlow + mobileConnectionsRepo.subscriptionsFlow /** * Generally, SystemUI wants to show iconography for each subscription that is listed by @@ -119,13 +131,13 @@ constructor( * subscription Id. This mapping is the same for every subscription. */ override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> = - mobileSubscriptionRepo.defaultDataSubRatConfig + mobileConnectionsRepo.defaultDataSubRatConfig .map { mobileMappingsProxy.mapIconSets(it) } .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf()) /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ override val defaultMobileIconGroup: StateFlow<MobileIconGroup> = - mobileSubscriptionRepo.defaultDataSubRatConfig + mobileConnectionsRepo.defaultDataSubRatConfig .map { mobileMappingsProxy.getDefaultIcons(it) } .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G) @@ -137,6 +149,6 @@ constructor( defaultMobileIconMapping, defaultMobileIconGroup, mobileMappingsProxy, - mobileSubscriptionRepo.getRepoForSubId(subId), + mobileConnectionsRepo.getRepoForSubId(subId), ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index cc8f6dd08585..81317398f086 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map /** * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over @@ -59,12 +58,18 @@ constructor( /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ var networkTypeIcon: Flow<Icon?> = - iconInteractor.networkTypeIconGroup.map { - val desc = - if (it.dataContentDescription != 0) - ContentDescription.Resource(it.dataContentDescription) - else null - Icon.Resource(it.dataType, desc) + combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) { + networkTypeIconGroup, + isDataEnabled -> + if (!isDataEnabled) { + null + } else { + val desc = + if (networkTypeIconGroup.dataContentDescription != 0) + ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) + else null + Icon.Resource(networkTypeIconGroup.dataType, desc) + } } var tint: Flow<Int> = flowOf(Color.CYAN) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 6ff7b7ccd5e3..de1fec85360b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -24,7 +24,14 @@ class FakeMobileConnectionRepository : MobileConnectionRepository { private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel()) override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow + private val _dataEnabled = MutableStateFlow(true) + override val dataEnabled = _dataEnabled + fun setMobileSubscriptionModel(model: MobileSubscriptionModel) { _subscriptionsModelFlow.value = model } + + fun setDataEnabled(enabled: Boolean) { + _dataEnabled.value = enabled + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index c88d468f1755..813e750684a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -50,7 +50,7 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository { _activeMobileDataSubscriptionId.value = subId } - fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) { - subIdRepos[subId] = repo + fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) { + connections.forEach { entry -> subIdRepos[entry.key] = entry.value } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt index 775e6dbb5e19..093936444789 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt @@ -20,16 +20,20 @@ import android.telephony.CellSignalStrengthCdma import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.SubscriptionInfo -import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.DATA_CONNECTED +import android.telephony.TelephonyManager.DATA_CONNECTING +import android.telephony.TelephonyManager.DATA_DISCONNECTED +import android.telephony.TelephonyManager.DATA_DISCONNECTING import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType @@ -59,7 +63,6 @@ import org.mockito.MockitoAnnotations class MobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionRepositoryImpl - @Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: ConnectivityPipelineLogger @@ -148,16 +151,61 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test - fun testFlowForSubId_dataConnectionState() = + fun testFlowForSubId_dataConnectionState_connected() = runBlocking(IMMEDIATE) { var latest: MobileSubscriptionModel? = null val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() - callback.onDataConnectionStateChanged(100, 200 /* unused */) + callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */) - assertThat(latest?.dataConnectionState).isEqualTo(100) + assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected) + + job.cancel() + } + + @Test + fun testFlowForSubId_dataConnectionState_connecting() = + runBlocking(IMMEDIATE) { + var latest: MobileSubscriptionModel? = null + val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + + val callback = + getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() + callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */) + + assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting) + + job.cancel() + } + + @Test + fun testFlowForSubId_dataConnectionState_disconnected() = + runBlocking(IMMEDIATE) { + var latest: MobileSubscriptionModel? = null + val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + + val callback = + getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() + callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */) + + assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected) + + job.cancel() + } + + @Test + fun testFlowForSubId_dataConnectionState_disconnecting() = + runBlocking(IMMEDIATE) { + var latest: MobileSubscriptionModel? = null + val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + + val callback = + getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() + callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */) + + assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting) job.cancel() } @@ -241,6 +289,32 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun dataEnabled_isEnabled() = + runBlocking(IMMEDIATE) { + whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true) + + var latest: Boolean? = null + val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun dataEnabled_isDisabled() = + runBlocking(IMMEDIATE) { + whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false) + + var latest: Boolean? = null + val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + private fun getTelephonyCallbacks(): List<TelephonyCallback> { val callbackCaptor = argumentCaptor<TelephonyCallback>() Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index cd4dbebcc35c..5611c448c550 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -22,19 +22,22 @@ import com.android.settingslib.mobile.TelephonyIcons import kotlinx.coroutines.flow.MutableStateFlow class FakeMobileIconInteractor : MobileIconInteractor { - private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN) + private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G) override val networkTypeIconGroup = _iconGroup - private val _isEmergencyOnly = MutableStateFlow<Boolean>(false) + private val _isEmergencyOnly = MutableStateFlow(false) override val isEmergencyOnly = _isEmergencyOnly - private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) + private val _isDataEnabled = MutableStateFlow(true) + override val isDataEnabled = _isDataEnabled + + private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) override val level = _level - private val _numberOfLevels = MutableStateFlow<Int>(4) + private val _numberOfLevels = MutableStateFlow(4) override val numberOfLevels = _numberOfLevels - private val _cutOut = MutableStateFlow<Boolean>(false) + private val _cutOut = MutableStateFlow(false) override val cutOut = _cutOut fun setIconGroup(group: SignalIcon.MobileIconGroup) { @@ -45,6 +48,10 @@ class FakeMobileIconInteractor : MobileIconInteractor { _isEmergencyOnly.value = emergency } + fun setIsDataEnabled(enabled: Boolean) { + _isDataEnabled.value = enabled + } + fun setLevel(level: Int) { _level.value = level } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index b01efd18971f..877ce0e6b351 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.SubscriptionInfo import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +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.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy @@ -41,7 +42,7 @@ import org.mockito.MockitoAnnotations class MobileIconsInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconsInteractor private val userSetupRepository = FakeUserSetupRepository() - private val subscriptionsRepository = FakeMobileConnectionsRepository() + private val connectionsRepository = FakeMobileConnectionsRepository() private val mobileMappingsProxy = FakeMobileMappingsProxy() private val scope = CoroutineScope(IMMEDIATE) @@ -50,9 +51,20 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + + connectionsRepository.setMobileConnectionRepositoryMap( + mapOf( + SUB_1_ID to CONNECTION_1, + SUB_2_ID to CONNECTION_2, + SUB_3_ID to CONNECTION_3, + SUB_4_ID to CONNECTION_4, + ) + ) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) + underTest = MobileIconsInteractorImpl( - subscriptionsRepository, + connectionsRepository, carrierConfigTracker, mobileMappingsProxy, userSetupRepository, @@ -76,7 +88,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = runBlocking(IMMEDIATE) { - subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) var latest: List<SubscriptionInfo>? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) @@ -89,8 +101,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() = runBlocking(IMMEDIATE) { - subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) - subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) @@ -106,8 +118,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() = runBlocking(IMMEDIATE) { - subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) - subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) + connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) @@ -123,8 +135,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() = runBlocking(IMMEDIATE) { - subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) - subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) @@ -141,8 +153,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() = runBlocking(IMMEDIATE) { - subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) - subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) @@ -162,10 +174,12 @@ class MobileIconsInteractorTest : SysuiTestCase() { private const val SUB_1_ID = 1 private val SUB_1 = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } + private val CONNECTION_1 = FakeMobileConnectionRepository() private const val SUB_2_ID = 2 private val SUB_2 = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } + private val CONNECTION_2 = FakeMobileConnectionRepository() private const val SUB_3_ID = 3 private val SUB_3_OPP = @@ -173,6 +187,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(it.subscriptionId).thenReturn(SUB_3_ID) whenever(it.isOpportunistic).thenReturn(true) } + private val CONNECTION_3 = FakeMobileConnectionRepository() private const val SUB_4_ID = 4 private val SUB_4_OPP = @@ -180,5 +195,6 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(it.subscriptionId).thenReturn(SUB_4_ID) whenever(it.isOpportunistic).thenReturn(true) } + private val CONNECTION_4 = FakeMobileConnectionRepository() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index b374abbd5082..ce0f33f400ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.test.filters.SmallTest import com.android.settingslib.graph.SignalDrawable -import com.android.settingslib.mobile.TelephonyIcons +import com.android.settingslib.mobile.TelephonyIcons.THREE_G 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.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.google.common.truth.Truth.assertThat @@ -27,6 +29,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -44,7 +47,7 @@ class MobileIconViewModelTest : SysuiTestCase() { interactor.apply { setLevel(1) setCutOut(false) - setIconGroup(TelephonyIcons.THREE_G) + setIconGroup(THREE_G) setIsEmergencyOnly(false) setNumberOfLevels(4) } @@ -62,6 +65,60 @@ class MobileIconViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun networkType_dataEnabled_groupIsRepresented() = + runBlocking(IMMEDIATE) { + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription) + ) + interactor.setIconGroup(THREE_G) + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_nullWhenDisabled() = + runBlocking(IMMEDIATE) { + interactor.setIconGroup(THREE_G) + interactor.setIsDataEnabled(false) + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun networkType_null_changeToDisabled() = + runBlocking(IMMEDIATE) { + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription) + ) + interactor.setIconGroup(THREE_G) + interactor.setIsDataEnabled(true) + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(expected) + + interactor.setIsDataEnabled(false) + yield() + + assertThat(latest).isNull() + + job.cancel() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate private const val SUB_1_ID = 1 |