From 09c154b291770f339624856ca50f821e39db8f48 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 26 Oct 2022 17:59:25 -0400 Subject: [PATCH] [SB refactor] Add connectivity tracking to the mobile pipeline This CL tracks 2 bits from ConnectivityManager for mobile networks: isConnected and isValidated. These bits are used to know whether or not cellular networks are the default transport, and whether or not connectivitymanager has validated that transport. Test: manual Test: atest packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/* Bug: 240492102 Change-Id: I13890cdc59c198c1b5290ae92e7ec4ba491999e3 --- .../data/model/MobileConnectivityModel.kt | 27 ++++++ .../repository/MobileConnectionsRepository.kt | 42 ++++++++ .../domain/interactor/MobileIconInteractor.kt | 5 +- .../interactor/MobileIconsInteractor.kt | 18 ++++ .../ui/viewmodel/MobileIconViewModel.kt | 10 +- .../FakeMobileConnectionsRepository.kt | 8 ++ .../MobileConnectionsRepositoryTest.kt | 97 +++++++++++++++++++ .../interactor/FakeMobileIconInteractor.kt | 7 ++ .../interactor/FakeMobileIconsInteractor.kt | 2 + .../interactor/MobileIconInteractorTest.kt | 15 +++ .../interactor/MobileIconsInteractorTest.kt | 42 ++++++++ .../ui/viewmodel/MobileIconViewModelTest.kt | 15 +++ 12 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt new file mode 100644 index 0000000000000..e61890523ebba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt @@ -0,0 +1,27 @@ +/* + * 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.net.NetworkCapabilities + +/** Provides information about a mobile network connection */ +data class MobileConnectivityModel( + /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */ + val isConnected: Boolean = false, + /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */ + val isValidated: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index 89edc8a873b96..c3c1f1403c606 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -16,9 +16,16 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.annotation.SuppressLint import android.content.Context import android.content.IntentFilter import android.database.ContentObserver +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR import android.provider.Settings import android.provider.Settings.Global.MOBILE_DATA import android.telephony.CarrierConfigManager @@ -37,6 +44,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject @@ -73,6 +81,9 @@ interface MobileConnectionsRepository { /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */ val defaultDataSubId: StateFlow + /** The current connectivity status for the default mobile network connection */ + val defaultMobileNetworkConnectivity: StateFlow + /** Get or create a repository for the line of service for the given subscription ID */ fun getRepoForSubId(subId: Int): MobileConnectionRepository @@ -86,6 +97,7 @@ interface MobileConnectionsRepository { class MobileConnectionsRepositoryImpl @Inject constructor( + private val connectivityManager: ConnectivityManager, private val subscriptionManager: SubscriptionManager, private val telephonyManager: TelephonyManager, private val logger: ConnectivityPipelineLogger, @@ -212,6 +224,36 @@ constructor( awaitClose { context.contentResolver.unregisterContentObserver(observer) } } + @SuppressLint("MissingPermission") + override val defaultMobileNetworkConnectivity: StateFlow = + conflatedCallbackFlow { + val callback = + object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { + override fun onLost(network: Network) { + // Send a disconnected model when lost. Maybe should create a sealed + // type or null here? + trySend(MobileConnectivityModel()) + } + + override fun onCapabilitiesChanged( + network: Network, + caps: NetworkCapabilities + ) { + trySend( + MobileConnectivityModel( + isConnected = caps.hasTransport(TRANSPORT_CELLULAR), + isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED), + ) + ) + } + } + + connectivityManager.registerDefaultNetworkCallback(callback) + + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) + private fun isValidSubId(subId: Int): Boolean { subscriptionsFlow.value.forEach { if (it.subscriptionId == subId) { 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 dd8a91fe58ed4..31dfb7ea7527c 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 @@ -26,7 +26,6 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -35,6 +34,9 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn interface MobileIconInteractor { + /** Only true if mobile is the default transport but is not validated, otherwise false */ + val isDefaultConnectionFailed: StateFlow + // TODO(b/256839546): clarify naming of default vs active /** True if we want to consider the data connection enabled */ val isDefaultDataEnabled: StateFlow @@ -63,6 +65,7 @@ class MobileIconInteractorImpl( defaultSubscriptionHasDataEnabled: StateFlow, defaultMobileIconMapping: StateFlow>, defaultMobileIconGroup: StateFlow, + override val isDefaultConnectionFailed: StateFlow, mobileMappingsProxy: MobileMappingsProxy, connectionRepository: MobileConnectionRepository, ) : MobileIconInteractor { 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 9d30ec05aad31..a4175c3a6ab19 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 @@ -60,6 +60,8 @@ interface MobileIconsInteractor { val defaultMobileIconMapping: StateFlow> /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ val defaultMobileIconGroup: StateFlow + /** True only if the default network is mobile, and validation also failed */ + val isDefaultConnectionFailed: StateFlow /** True once the user has been set up */ val isUserSetup: StateFlow /** @@ -162,6 +164,21 @@ constructor( .mapLatest { mobileMappingsProxy.getDefaultIcons(it) } .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G) + /** + * We want to show an error state when cellular has actually failed to validate, but not if some + * other transport type is active, because then we expect there not to be validation. + */ + override val isDefaultConnectionFailed: StateFlow = + mobileConnectionsRepo.defaultMobileNetworkConnectivity + .mapLatest { connectivityModel -> + if (!connectivityModel.isConnected) { + false + } else { + !connectivityModel.isValidated + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val isUserSetup: StateFlow = userSetupRepo.isUserSetupFlow /** Vends out new [MobileIconInteractor] for a particular subId */ @@ -171,6 +188,7 @@ constructor( activeDataConnectionHasDataEnabled, defaultMobileIconMapping, defaultMobileIconGroup, + isDefaultConnectionFailed, mobileMappingsProxy, 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 b256665358d44..157b0252779c4 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 @@ -66,10 +66,12 @@ constructor( /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ val networkTypeIcon: Flow = - combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) { - networkTypeIconGroup, - isDataEnabled -> - if (!isDataEnabled) { + combine( + iconInteractor.networkTypeIconGroup, + iconInteractor.isDataEnabled, + iconInteractor.isDefaultConnectionFailed + ) { networkTypeIconGroup, isDataEnabled, isFailedConnection -> + if (!isDataEnabled || isFailedConnection) { null } else { val desc = 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 8488effd22ee3..533d5d9d5b4a3 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 @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.settingslib.mobile.MobileMappings.Config +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -35,6 +36,9 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository { private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) override val defaultDataSubId = _defaultDataSubId + private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel()) + override val defaultMobileNetworkConnectivity = _mobileConnectivity + private val subIdRepos = mutableMapOf() override fun getRepoForSubId(subId: Int): MobileConnectionRepository { return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it } @@ -55,6 +59,10 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository { _defaultDataSubId.value = id } + fun setMobileConnectivity(model: MobileConnectivityModel) { + _mobileConnectivity.value = model + } + suspend fun triggerGlobalMobileDataSettingChangedEvent() { _globalMobileDataSettingChangedEvent.emit(Unit) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt index 27c7357cb9958..a953a3d802e61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt @@ -17,6 +17,11 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import android.content.Intent +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR import android.provider.Settings import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager @@ -26,6 +31,7 @@ import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import com.android.internal.telephony.PhoneConstants import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -54,6 +60,7 @@ import org.mockito.MockitoAnnotations class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionsRepositoryImpl + @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: ConnectivityPipelineLogger @@ -67,6 +74,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { underTest = MobileConnectionsRepositoryImpl( + connectivityManager, subscriptionManager, telephonyManager, logger, @@ -235,6 +243,29 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun mobileConnectivity_default() { + assertThat(underTest.defaultMobileNetworkConnectivity.value) + .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false)) + } + + @Test + fun mobileConnectivity_isConnected_isValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = true, validated = true) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest) + .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true)) + + job.cancel() + } + @Test fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() = runBlocking(IMMEDIATE) { @@ -253,6 +284,69 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun mobileConnectivity_isConnected_isNotValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = true, validated = false) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest) + .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false)) + + job.cancel() + } + + @Test + fun mobileConnectivity_isNotConnected_isNotValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = false, validated = false) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest) + .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false)) + + job.cancel() + } + + /** In practice, I don't think this state can ever happen (!connected, validated) */ + @Test + fun mobileConnectivity_isNotConnected_isValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = false, validated = true) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest).isEqualTo(MobileConnectivityModel(false, true)) + + job.cancel() + } + + private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities = + mock().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected) + whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated) + } + + private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + val callbackCaptor = argumentCaptor() + verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture()) + return callbackCaptor.value!! + } + private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener { val callbackCaptor = argumentCaptor() verify(subscriptionManager) @@ -281,5 +375,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private const val SUB_2_ID = 2 private val SUB_2 = mock().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } + + private const val NET_ID = 123 + private val NETWORK = mock().apply { whenever(getNetId()).thenReturn(NET_ID) } } } 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 ea3faa0dff1aa..b05a284b864cd 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 @@ -28,6 +28,9 @@ class FakeMobileIconInteractor : MobileIconInteractor { private val _isEmergencyOnly = MutableStateFlow(false) override val isEmergencyOnly = _isEmergencyOnly + private val _isFailedConnection = MutableStateFlow(false) + override val isDefaultConnectionFailed = _isFailedConnection + private val _isDataEnabled = MutableStateFlow(true) override val isDataEnabled = _isDataEnabled @@ -56,6 +59,10 @@ class FakeMobileIconInteractor : MobileIconInteractor { _isDefaultDataEnabled.value = disabled } + fun setIsFailedConnection(failed: Boolean) { + _isFailedConnection.value = failed + } + fun setLevel(level: Int) { _level.value = level } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index cf95bf736bab1..061c3b54650e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -45,6 +45,8 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G, ) + override val isDefaultConnectionFailed = MutableStateFlow(false) + private val _filteredSubscriptions = MutableStateFlow>(listOf()) override val filteredSubscriptions = _filteredSubscriptions diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 8dd7ef88a0fb6..ddc0bc78db9f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -60,6 +60,7 @@ class MobileIconInteractorTest : SysuiTestCase() { mobileIconsInteractor.activeDataConnectionHasDataEnabled, mobileIconsInteractor.defaultMobileIconMapping, mobileIconsInteractor.defaultMobileIconGroup, + mobileIconsInteractor.isDefaultConnectionFailed, mobileMappingsProxy, connectionRepository, ) @@ -216,6 +217,20 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun test_isDefaultConnectionFailed_matchedParent() = + runBlocking(IMMEDIATE) { + val job = underTest.isDefaultConnectionFailed.launchIn(this) + + mobileIconsInteractor.isDefaultConnectionFailed.value = false + assertThat(underTest.isDefaultConnectionFailed.value).isFalse() + + mobileIconsInteractor.isDefaultConnectionFailed.value = true + assertThat(underTest.isDefaultConnectionFailed.value).isTrue() + + job.cancel() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate 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 799d0a3f1cc72..b56dcd7525579 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 @@ -20,6 +20,7 @@ import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel 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 @@ -215,6 +216,47 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun failedConnection_connected_validated_notFailed() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true)) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun failedConnection_notConnected_notValidated_notFailed() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + + connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false)) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun failedConnection_connected_notValidated_failed() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + + connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false)) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate 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 da0bf5c274856..4eecbb9835ed8 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 @@ -47,6 +47,7 @@ class MobileIconViewModelTest : SysuiTestCase() { interactor.apply { setLevel(1) setIsDefaultDataEnabled(true) + setIsFailedConnection(false) setIconGroup(THREE_G) setIsEmergencyOnly(false) setNumberOfLevels(4) @@ -111,6 +112,20 @@ class MobileIconViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun networkType_nullWhenFailedConnection() = + runBlocking(IMMEDIATE) { + interactor.setIconGroup(THREE_G) + interactor.setIsDataEnabled(true) + interactor.setIsFailedConnection(true) + 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) {