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 003df2482c6e4..f85a1213e9287 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 @@ -21,6 +21,7 @@ import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository @@ -41,12 +42,29 @@ interface MobileIconInteractor { /** The current mobile data activity */ val activity: Flow + /** + * This bit is meant to be `true` if and only if the default network capabilities (see + * [android.net.ConnectivityManager.registerDefaultNetworkCallback]) result in a network that + * has the [android.net.NetworkCapabilities.TRANSPORT_CELLULAR] represented. + * + * Note that this differs from [isDataConnected], which is tracked by telephony and has to do + * with the state of using this mobile connection for data as opposed to just voice. It is + * possible for a mobile subscription to be connected but not be in a connected data state, and + * thus we wouldn't want to show the network type icon. + */ + val isConnected: Flow + + /** + * True when telephony tells us that the data state is CONNECTED. See + * [android.telephony.TelephonyCallback.DataConnectionStateListener] for more details. We + * consider this connection to be serving data, and thus want to show a network type icon, when + * data is connected. Other data connection states would typically cause us not to show the icon + */ + val isDataConnected: StateFlow + /** Only true if mobile is the default transport but is not validated, otherwise false */ val isDefaultConnectionFailed: StateFlow - /** True when telephony tells us that the data state is CONNECTED */ - val isDataConnected: StateFlow - /** True if we consider this connection to be in service, i.e. can make calls */ val isInService: StateFlow @@ -100,6 +118,7 @@ class MobileIconInteractorImpl( defaultSubscriptionHasDataEnabled: StateFlow, override val alwaysShowDataRatIcon: StateFlow, override val alwaysUseCdmaLevel: StateFlow, + defaultMobileConnectivity: StateFlow, defaultMobileIconMapping: StateFlow>, defaultMobileIconGroup: StateFlow, override val isDefaultConnectionFailed: StateFlow, @@ -111,6 +130,8 @@ class MobileIconInteractorImpl( override val activity = connectionInfo.mapLatest { it.dataActivityDirection } + override val isConnected: Flow = defaultMobileConnectivity.mapLatest { it.isConnected } + override val isDataEnabled: StateFlow = connectionRepository.dataEnabled override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled 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 83da1dd067806..96196e72b1031 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,6 +23,7 @@ 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.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository @@ -62,6 +63,14 @@ interface MobileIconsInteractor { /** True if the CDMA level should be preferred over the primary level. */ val alwaysUseCdmaLevel: StateFlow + /** + * The connectivity of the default mobile network. Note that this can differ from what is + * reported from [MobileConnectionsRepository] in some cases. E.g., when the active subscription + * changes but the groupUuid remains the same, we keep the old validation information for 2 + * seconds to avoid icon flickering. + */ + val defaultMobileNetworkConnectivity: StateFlow + /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ val defaultMobileIconMapping: StateFlow> /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ @@ -154,6 +163,9 @@ constructor( } } + override val defaultMobileNetworkConnectivity: StateFlow = + mobileConnectionsRepo.defaultMobileNetworkConnectivity + /** * Mapping from network type to [MobileIconGroup] using the config generated for the default * subscription Id. This mapping is the same for every subscription. @@ -207,6 +219,7 @@ constructor( activeDataConnectionHasDataEnabled, alwaysShowDataRatIcon, alwaysUseCdmaLevel, + defaultMobileNetworkConnectivity, defaultMobileIconMapping, defaultMobileIconGroup, isDefaultConnectionFailed, 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 a2117c7df188e..5e9356163e6e5 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 @@ -102,24 +102,29 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), initial) } + private val showNetworkTypeIcon: Flow = + combine( + iconInteractor.isDataConnected, + iconInteractor.isDataEnabled, + iconInteractor.isDefaultConnectionFailed, + iconInteractor.alwaysShowDataRatIcon, + iconInteractor.isConnected, + ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected -> + alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected) + } + override val networkTypeIcon: Flow = combine( iconInteractor.networkTypeIconGroup, - iconInteractor.isDataConnected, - iconInteractor.isDataEnabled, - iconInteractor.isDefaultConnectionFailed, - iconInteractor.alwaysShowDataRatIcon, - ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow -> + showNetworkTypeIcon, + ) { networkTypeIconGroup, shouldShow -> val desc = if (networkTypeIconGroup.dataContentDescription != 0) ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) else null val icon = Icon.Resource(networkTypeIconGroup.dataType, desc) return@combine when { - alwaysShow -> icon - !dataConnected -> null - !dataEnabled -> null - failedConnection -> null + !shouldShow -> null else -> icon } } 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 a29146b016680..7aeaa48165aa6 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 @@ -40,6 +40,8 @@ class FakeMobileIconInteractor( ) ) + override val isConnected = MutableStateFlow(true) + private val _iconGroup = MutableStateFlow(TelephonyIcons.THREE_G) override val networkTypeIconGroup = _iconGroup 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 1c0064610c52f..e57ad768ec8a9 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 @@ -23,6 +23,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import kotlinx.coroutines.flow.MutableStateFlow @@ -60,6 +61,8 @@ class FakeMobileIconsInteractor( override val alwaysUseCdmaLevel = MutableStateFlow(false) + override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel()) + private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING) override val defaultMobileIconMapping = _defaultMobileIconMapping 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 e6be7f15235b3..a774ffeb997a2 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 @@ -61,6 +61,7 @@ class MobileIconInteractorTest : SysuiTestCase() { mobileIconsInteractor.activeDataConnectionHasDataEnabled, mobileIconsInteractor.alwaysShowDataRatIcon, mobileIconsInteractor.alwaysUseCdmaLevel, + mobileIconsInteractor.defaultMobileNetworkConnectivity, mobileIconsInteractor.defaultMobileIconMapping, mobileIconsInteractor.defaultMobileIconGroup, mobileIconsInteractor.isDefaultConnectionFailed, 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 721d49a13834e..9e8ae6db449a3 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 @@ -327,6 +327,32 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun `default mobile connectivity - uses repo value`() = + testScope.runTest { + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + var expected = MobileConnectivityModel(isConnected = true, isValidated = true) + connectionsRepository.setMobileConnectivity(expected) + assertThat(latest).isEqualTo(expected) + + expected = MobileConnectivityModel(isConnected = false, isValidated = true) + connectionsRepository.setMobileConnectivity(expected) + assertThat(latest).isEqualTo(expected) + + expected = MobileConnectivityModel(isConnected = true, isValidated = false) + connectionsRepository.setMobileConnectivity(expected) + assertThat(latest).isEqualTo(expected) + + expected = MobileConnectivityModel(isConnected = false, isValidated = false) + connectionsRepository.setMobileConnectivity(expected) + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + companion object { private val tableLogBuffer = TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock()) 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 2a8d42ff69975..a24e29aebc1ef 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 @@ -273,6 +273,41 @@ class MobileIconViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun `network type - alwaysShow - shown when not connected`() = + testScope.runTest { + interactor.setIconGroup(THREE_G) + interactor.isConnected.value = false + interactor.alwaysShowDataRatIcon.value = 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() + } + + @Test + fun `network type - not shown when not connected`() = + testScope.runTest { + interactor.setIconGroup(THREE_G) + interactor.isDataConnected.value = true + interactor.isConnected.value = false + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + @Test fun roaming() = testScope.runTest {