Merge changes I1c9ade7e,I06365d19,I0945a29d,I8e798197,Ib3a4f14b, ... into tm-qpr-dev

* changes:
  [Sb refactor] [demo] Use better default for demo mode
  [Sb refactor] Support NOT_DEFAULT_DATA network type
  Partial revert of "[SB Refactor] Remove unused `isDefaultDataSubscription` flow."
  [Sb refactor] Implement 2s grace period for data switching
  [Sb refactor] Add the default network's connectivity to the view model
  [Sb refactor] Upgrade MobileIconsInteractorTest to use testScope
This commit is contained in:
Evan Laird
2023-01-17 21:51:57 +00:00
committed by Android (Google) Code Review
15 changed files with 615 additions and 39 deletions

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.provider.Settings import android.provider.Settings
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config import com.android.settingslib.mobile.MobileMappings.Config
@@ -37,6 +38,15 @@ interface MobileConnectionsRepository {
/** Observable for the subscriptionId of the current mobile data connection */ /** Observable for the subscriptionId of the current mobile data connection */
val activeMobileDataSubscriptionId: StateFlow<Int> val activeMobileDataSubscriptionId: StateFlow<Int>
/**
* Observable event for when the active data sim switches but the group stays the same. E.g.,
* CBRS switching would trigger this
*/
val activeSubChangedInGroupEvent: Flow<Unit>
/** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
val defaultDataSubId: StateFlow<Int>
/** The current connectivity status for the default mobile network connection */ /** The current connectivity status for the default mobile network connection */
val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>

View File

@@ -124,6 +124,9 @@ constructor(
realRepository.activeMobileDataSubscriptionId.value realRepository.activeMobileDataSubscriptionId.value
) )
override val activeSubChangedInGroupEvent: Flow<Unit> =
activeRepo.flatMapLatest { it.activeSubChangedInGroupEvent }
override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> = override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
activeRepo activeRepo
.flatMapLatest { it.defaultDataSubRatConfig } .flatMapLatest { it.defaultDataSubRatConfig }
@@ -139,6 +142,11 @@ constructor(
override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> = override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
activeRepo.flatMapLatest { it.defaultMobileIconGroup } activeRepo.flatMapLatest { it.defaultMobileIconGroup }
override val defaultDataSubId: StateFlow<Int> =
activeRepo
.flatMapLatest { it.defaultDataSubId }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> = override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
activeRepo activeRepo
.flatMapLatest { it.defaultMobileNetworkConnectivity } .flatMapLatest { it.defaultMobileNetworkConnectivity }

View File

@@ -48,6 +48,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -120,6 +121,9 @@ constructor(
subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
) )
// TODO(b/261029387): consider adding a demo command for this
override val activeSubChangedInGroupEvent: Flow<Unit> = flowOf()
/** Demo mode doesn't currently support modifications to the mobile mappings */ /** Demo mode doesn't currently support modifications to the mobile mappings */
override val defaultDataSubRatConfig = override val defaultDataSubRatConfig =
MutableStateFlow(MobileMappings.Config.readConfig(context)) MutableStateFlow(MobileMappings.Config.readConfig(context))
@@ -148,8 +152,12 @@ constructor(
private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key } private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
// TODO(b/261029387): add a command for this value
override val defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
// TODO(b/261029387): not yet supported // TODO(b/261029387): not yet supported
override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel()) override val defaultMobileNetworkConnectivity =
MutableStateFlow(MobileConnectivityModel(isConnected = true, isValidated = true))
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository { override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
val current = connectionRepoCache[subId]?.repo val current = connectionRepoCache[subId]?.repo
@@ -229,6 +237,9 @@ constructor(
val connection = getRepoForSubId(subId) val connection = getRepoForSubId(subId)
connectionRepoCache[subId]?.lastMobileState = state connectionRepoCache[subId]?.lastMobileState = state
// TODO(b/261029387): until we have a command, use the most recent subId
defaultDataSubId.value = subId
// This is always true here, because we split out disabled states at the data-source level // This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true connection.dataEnabled.value = true
connection.networkName.value = NetworkNameModel.Derived(state.name) connection.networkName.value = NetworkNameModel.Derived(state.name)

View File

@@ -35,6 +35,7 @@ import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.R import com.android.systemui.R
@@ -52,6 +53,7 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.util.kotlin.pairwiseBy
import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@@ -60,9 +62,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
@@ -159,10 +164,24 @@ constructor(
.logInputChange(logger, "onActiveDataSubscriptionIdChanged") .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID) .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
private val defaultDataSubIdChangedEvent = private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1)
override val defaultDataSubId: StateFlow<Int> =
broadcastDispatcher broadcastDispatcher
.broadcastFlow(IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) .broadcastFlow(
IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
) { intent, _ ->
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
}
.distinctUntilChanged()
.logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED") .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
SubscriptionManager.getDefaultDataSubscriptionId()
)
private val carrierConfigChangedEvent = private val carrierConfigChangedEvent =
broadcastDispatcher broadcastDispatcher
@@ -170,7 +189,7 @@ constructor(
.logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED") .logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
override val defaultDataSubRatConfig: StateFlow<Config> = override val defaultDataSubRatConfig: StateFlow<Config> =
merge(defaultDataSubIdChangedEvent, carrierConfigChangedEvent) merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) } .mapLatest { Config.readConfig(context) }
.distinctUntilChanged() .distinctUntilChanged()
.logInputChange(logger, "defaultDataSubRatConfig") .logInputChange(logger, "defaultDataSubRatConfig")
@@ -258,6 +277,35 @@ constructor(
.logInputChange(logger, "defaultMobileNetworkConnectivity") .logInputChange(logger, "defaultMobileNetworkConnectivity")
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
/**
* Flow that tracks the active mobile data subscriptions. Emits `true` whenever the active data
* subscription Id changes but the subscription group remains the same. In these cases, we want
* to retain the previous subscription's validation status for up to 2s to avoid flickering the
* icon.
*
* TODO(b/265164432): we should probably expose all change events, not just same group
*/
@SuppressLint("MissingPermission")
override val activeSubChangedInGroupEvent =
flow {
activeMobileDataSubscriptionId.pairwiseBy { prevVal: Int, newVal: Int ->
if (!defaultMobileNetworkConnectivity.value.isValidated) {
return@pairwiseBy
}
val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal)
val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal)
if (prevSub == null || nextSub == null) {
return@pairwiseBy
}
if (prevSub.groupUuid != null && prevSub.groupUuid == nextSub.groupUuid) {
emit(Unit)
}
}
}
.flowOn(bgDispatcher)
private fun isValidSubId(subId: Int): Boolean { private fun isValidSubId(subId: Int): Boolean {
subscriptions.value.forEach { subscriptions.value.forEach {
if (it.subscriptionId == subId) { if (it.subscriptionId == subId) {

View File

@@ -18,9 +18,11 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons.NOT_DEFAULT_DATA
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer 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.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.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -41,12 +43,29 @@ interface MobileIconInteractor {
/** The current mobile data activity */ /** The current mobile data activity */
val activity: Flow<DataActivityModel> val activity: Flow<DataActivityModel>
/**
* 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<Boolean>
/**
* 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<Boolean>
/** Only true if mobile is the default transport but is not validated, otherwise false */ /** Only true if mobile is the default transport but is not validated, otherwise false */
val isDefaultConnectionFailed: StateFlow<Boolean> val isDefaultConnectionFailed: StateFlow<Boolean>
/** True when telephony tells us that the data state is CONNECTED */
val isDataConnected: StateFlow<Boolean>
/** True if we consider this connection to be in service, i.e. can make calls */ /** True if we consider this connection to be in service, i.e. can make calls */
val isInService: StateFlow<Boolean> val isInService: StateFlow<Boolean>
@@ -100,8 +119,10 @@ class MobileIconInteractorImpl(
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>, defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
override val alwaysShowDataRatIcon: StateFlow<Boolean>, override val alwaysShowDataRatIcon: StateFlow<Boolean>,
override val alwaysUseCdmaLevel: StateFlow<Boolean>, override val alwaysUseCdmaLevel: StateFlow<Boolean>,
defaultMobileConnectivity: StateFlow<MobileConnectivityModel>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>, defaultMobileIconGroup: StateFlow<MobileIconGroup>,
defaultDataSubId: StateFlow<Int>,
override val isDefaultConnectionFailed: StateFlow<Boolean>, override val isDefaultConnectionFailed: StateFlow<Boolean>,
connectionRepository: MobileConnectionRepository, connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor { ) : MobileIconInteractor {
@@ -111,8 +132,19 @@ class MobileIconInteractorImpl(
override val activity = connectionInfo.mapLatest { it.dataActivityDirection } override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
private val isDefault =
defaultDataSubId
.mapLatest { connectionRepository.subId == it }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
connectionRepository.subId == defaultDataSubId.value
)
override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
override val networkName = override val networkName =
@@ -137,7 +169,12 @@ class MobileIconInteractorImpl(
connectionInfo, connectionInfo,
defaultMobileIconMapping, defaultMobileIconMapping,
defaultMobileIconGroup, defaultMobileIconGroup,
) { info, mapping, defaultGroup -> isDefault,
) { info, mapping, defaultGroup, isDefault ->
if (!isDefault) {
return@combine NOT_DEFAULT_DATA
}
when (info.resolvedNetworkType) { when (info.resolvedNetworkType) {
is ResolvedNetworkType.CarrierMergedNetworkType -> is ResolvedNetworkType.CarrierMergedNetworkType ->
info.resolvedNetworkType.iconGroupOverride info.resolvedNetworkType.iconGroupOverride

View File

@@ -23,6 +23,7 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application 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.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository 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.MobileConnectionsRepository
@@ -31,14 +32,17 @@ import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
/** /**
* Business layer logic for the set of mobile subscription icons. * Business layer logic for the set of mobile subscription icons.
@@ -62,6 +66,17 @@ interface MobileIconsInteractor {
/** True if the CDMA level should be preferred over the primary level. */ /** True if the CDMA level should be preferred over the primary level. */
val alwaysUseCdmaLevel: StateFlow<Boolean> val alwaysUseCdmaLevel: StateFlow<Boolean>
/** Tracks the subscriptionId set as the default for data connections */
val defaultDataSubId: StateFlow<Int>
/**
* 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<MobileConnectivityModel>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
@@ -154,6 +169,48 @@ constructor(
} }
} }
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
/**
* Copied from the old pipeline. We maintain a 2s period of time where we will keep the
* validated bit from the old active network (A) while data is changing to the new one (B).
*
* This condition only applies if
* 1. A and B are in the same subscription group (e.c. for CBRS data switching) and
* 2. A was validated before the switch
*
* The goal of this is to minimize the flickering in the UI of the cellular indicator
*/
private val forcingCellularValidation =
mobileConnectionsRepo.activeSubChangedInGroupEvent
.filter { mobileConnectionsRepo.defaultMobileNetworkConnectivity.value.isValidated }
.transformLatest {
emit(true)
delay(2000)
emit(false)
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
combine(
mobileConnectionsRepo.defaultMobileNetworkConnectivity,
forcingCellularValidation,
) { networkConnectivity, forceValidation ->
return@combine if (forceValidation) {
MobileConnectivityModel(
isValidated = true,
isConnected = networkConnectivity.isConnected
)
} else {
networkConnectivity
}
}
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
mobileConnectionsRepo.defaultMobileNetworkConnectivity.value
)
/** /**
* Mapping from network type to [MobileIconGroup] using the config generated for the default * Mapping from network type to [MobileIconGroup] using the config generated for the default
* subscription Id. This mapping is the same for every subscription. * subscription Id. This mapping is the same for every subscription.
@@ -207,8 +264,10 @@ constructor(
activeDataConnectionHasDataEnabled, activeDataConnectionHasDataEnabled,
alwaysShowDataRatIcon, alwaysShowDataRatIcon,
alwaysUseCdmaLevel, alwaysUseCdmaLevel,
defaultMobileNetworkConnectivity,
defaultMobileIconMapping, defaultMobileIconMapping,
defaultMobileIconGroup, defaultMobileIconGroup,
defaultDataSubId,
isDefaultConnectionFailed, isDefaultConnectionFailed,
mobileConnectionsRepo.getRepoForSubId(subId), mobileConnectionsRepo.getRepoForSubId(subId),
) )

View File

@@ -102,24 +102,29 @@ constructor(
.stateIn(scope, SharingStarted.WhileSubscribed(), initial) .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
} }
private val showNetworkTypeIcon: Flow<Boolean> =
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<Icon?> = override val networkTypeIcon: Flow<Icon?> =
combine( combine(
iconInteractor.networkTypeIconGroup, iconInteractor.networkTypeIconGroup,
iconInteractor.isDataConnected, showNetworkTypeIcon,
iconInteractor.isDataEnabled, ) { networkTypeIconGroup, shouldShow ->
iconInteractor.isDefaultConnectionFailed,
iconInteractor.alwaysShowDataRatIcon,
) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
val desc = val desc =
if (networkTypeIconGroup.dataContentDescription != 0) if (networkTypeIconGroup.dataContentDescription != 0)
ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
else null else null
val icon = Icon.Resource(networkTypeIconGroup.dataType, desc) val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
return@combine when { return@combine when {
alwaysShow -> icon !shouldShow -> null
!dataConnected -> null
!dataEnabled -> null
failedConnection -> null
else -> icon else -> icon
} }
} }

View File

@@ -26,6 +26,7 @@ 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.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
@@ -56,6 +57,10 @@ class FakeMobileConnectionsRepository(
private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
override val activeSubChangedInGroupEvent: MutableSharedFlow<Unit> = MutableSharedFlow()
private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val defaultDataSubId = _defaultDataSubId
private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel()) private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
override val defaultMobileNetworkConnectivity = _mobileConnectivity override val defaultMobileNetworkConnectivity = _mobileConnectivity
@@ -81,6 +86,10 @@ class FakeMobileConnectionsRepository(
_subscriptions.value = subs _subscriptions.value = subs
} }
fun setDefaultDataSubId(id: Int) {
_defaultDataSubId.value = id
}
fun setMobileConnectivity(model: MobileConnectivityModel) { fun setMobileConnectivity(model: MobileConnectivityModel) {
_mobileConnectivity.value = model _mobileConnectivity.value = model
} }

View File

@@ -89,6 +89,14 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
underTest.startProcessingCommands() underTest.startProcessingCommands()
} }
@Test
fun `connectivity - defaults to connected and validated`() =
testScope.runTest {
val connectivity = underTest.defaultMobileNetworkConnectivity.value
assertThat(connectivity.isConnected).isTrue()
assertThat(connectivity.isValidated).isTrue()
}
@Test @Test
fun `network event - create new subscription`() = fun `network event - create new subscription`() =
testScope.runTest { testScope.runTest {

View File

@@ -478,6 +478,35 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun testDefaultDataSubId_updatesOnBroadcast() =
runBlocking(IMMEDIATE) {
var latest: Int? = null
val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(
context,
Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
.putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
)
}
assertThat(latest).isEqualTo(SUB_2_ID)
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(
context,
Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
.putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
)
}
assertThat(latest).isEqualTo(SUB_1_ID)
job.cancel()
}
@Test @Test
fun mobileConnectivity_default() { fun mobileConnectivity_default() {
assertThat(underTest.defaultMobileNetworkConnectivity.value) assertThat(underTest.defaultMobileNetworkConnectivity.value)

View File

@@ -40,6 +40,8 @@ class FakeMobileIconInteractor(
) )
) )
override val isConnected = MutableStateFlow(true)
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G) private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup override val networkTypeIconGroup = _iconGroup

View File

@@ -23,6 +23,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer 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.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,6 +60,9 @@ class FakeMobileIconsInteractor(
override val alwaysShowDataRatIcon = MutableStateFlow(false) override val alwaysShowDataRatIcon = MutableStateFlow(false)
override val alwaysUseCdmaLevel = MutableStateFlow(false) override val alwaysUseCdmaLevel = MutableStateFlow(false)
override val defaultDataSubId = MutableStateFlow(DEFAULT_DATA_SUB_ID)
override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING) private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping override val defaultMobileIconMapping = _defaultMobileIconMapping
@@ -77,6 +81,8 @@ class FakeMobileIconsInteractor(
companion object { companion object {
val DEFAULT_ICON = TelephonyIcons.G val DEFAULT_ICON = TelephonyIcons.G
const val DEFAULT_DATA_SUB_ID = 1
// Use [MobileMappings] to define some simple definitions // Use [MobileMappings] to define some simple definitions
const val THREE_G = NETWORK_TYPE_GSM const val THREE_G = NETWORK_TYPE_GSM
const val LTE = NETWORK_TYPE_LTE const val LTE = NETWORK_TYPE_LTE

View File

@@ -61,8 +61,10 @@ class MobileIconInteractorTest : SysuiTestCase() {
mobileIconsInteractor.activeDataConnectionHasDataEnabled, mobileIconsInteractor.activeDataConnectionHasDataEnabled,
mobileIconsInteractor.alwaysShowDataRatIcon, mobileIconsInteractor.alwaysShowDataRatIcon,
mobileIconsInteractor.alwaysUseCdmaLevel, mobileIconsInteractor.alwaysUseCdmaLevel,
mobileIconsInteractor.defaultMobileNetworkConnectivity,
mobileIconsInteractor.defaultMobileIconMapping, mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup, mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.defaultDataSubId,
mobileIconsInteractor.isDefaultConnectionFailed, mobileIconsInteractor.isDefaultConnectionFailed,
connectionRepository, connectionRepository,
) )
@@ -288,6 +290,30 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun `icon group - checks default data`() =
runBlocking(IMMEDIATE) {
mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID
connectionRepository.setConnectionInfo(
MobileConnectionModel(
resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
),
)
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
// Default data sub id changes to something else
mobileIconsInteractor.defaultDataSubId.value = 123
yield()
assertThat(latest).isEqualTo(TelephonyIcons.NOT_DEFAULT_DATA)
job.cancel()
}
@Test @Test
fun alwaysShowDataRatIcon_matchesParent() = fun alwaysShowDataRatIcon_matchesParent() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {

View File

@@ -31,11 +31,13 @@ import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@@ -43,13 +45,16 @@ import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest @SmallTest
class MobileIconsInteractorTest : SysuiTestCase() { class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor private lateinit var underTest: MobileIconsInteractor
private lateinit var connectionsRepository: FakeMobileConnectionsRepository private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private val userSetupRepository = FakeUserSetupRepository() private val userSetupRepository = FakeUserSetupRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val scope = CoroutineScope(IMMEDIATE)
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -73,7 +78,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
connectionsRepository, connectionsRepository,
carrierConfigTracker, carrierConfigTracker,
userSetupRepository, userSetupRepository,
scope testScope.backgroundScope,
) )
} }
@@ -81,7 +86,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_default() = fun filteredSubscriptions_default() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: List<SubscriptionModel>? = null var latest: List<SubscriptionModel>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
@@ -92,7 +97,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
runBlocking(IMMEDIATE) { testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
var latest: List<SubscriptionModel>? = null var latest: List<SubscriptionModel>? = null
@@ -105,7 +110,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() = fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
runBlocking(IMMEDIATE) { testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -122,7 +127,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() = fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
runBlocking(IMMEDIATE) { testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -139,7 +144,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() = fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
runBlocking(IMMEDIATE) { testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -157,7 +162,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() = fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
runBlocking(IMMEDIATE) { testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -175,7 +180,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun activeDataConnection_turnedOn() = fun activeDataConnection_turnedOn() =
runBlocking(IMMEDIATE) { testScope.runTest {
CONNECTION_1.setDataEnabled(true) CONNECTION_1.setDataEnabled(true)
var latest: Boolean? = null var latest: Boolean? = null
val job = val job =
@@ -188,7 +193,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun activeDataConnection_turnedOff() = fun activeDataConnection_turnedOff() =
runBlocking(IMMEDIATE) { testScope.runTest {
CONNECTION_1.setDataEnabled(true) CONNECTION_1.setDataEnabled(true)
var latest: Boolean? = null var latest: Boolean? = null
val job = val job =
@@ -204,7 +209,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun activeDataConnection_invalidSubId() = fun activeDataConnection_invalidSubId() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = val job =
underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
@@ -220,7 +225,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun failedConnection_connected_validated_notFailed() = fun failedConnection_connected_validated_notFailed() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true)) connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
@@ -233,7 +238,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun failedConnection_notConnected_notValidated_notFailed() = fun failedConnection_notConnected_notValidated_notFailed() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
@@ -247,7 +252,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun failedConnection_connected_notValidated_failed() = fun failedConnection_connected_notValidated_failed() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
@@ -261,7 +266,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun alwaysShowDataRatIcon_configHasTrue() = fun alwaysShowDataRatIcon_configHasTrue() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
@@ -277,7 +282,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun alwaysShowDataRatIcon_configHasFalse() = fun alwaysShowDataRatIcon_configHasFalse() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
@@ -293,7 +298,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun alwaysUseCdmaLevel_configHasTrue() = fun alwaysUseCdmaLevel_configHasTrue() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
@@ -309,7 +314,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun alwaysUseCdmaLevel_configHasFalse() = fun alwaysUseCdmaLevel_configHasFalse() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
@@ -323,8 +328,286 @@ class MobileIconsInteractorTest : SysuiTestCase() {
job.cancel() 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()
}
@Test
fun `data switch - in same group - validated matches previous value`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
// Trigger a data change in the same subscription group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
job.cancel()
}
@Test
fun `data switch - in same group - validated matches previous value - expires after 2s`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
// Trigger a data change in the same subscription group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
// After 1s, the force validation bit is still present
advanceTimeBy(1000)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
// After 2s, the force validation expires
advanceTimeBy(1001)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
job.cancel()
}
@Test
fun `data switch - in same group - not validated - uses new value immediately`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = false,
)
)
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
job.cancel()
}
@Test
fun `data switch - lose validation - then switch happens - clears forced bit`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
// GIVEN the network starts validated
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
// WHEN a data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// WHEN the validation bit is lost
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
// WHEN another data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// THEN the forced validation bit is still removed after 2s
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
advanceTimeBy(1000)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
advanceTimeBy(1001)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
job.cancel()
}
@Test
fun `data switch - while already forcing validation - resets clock`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
advanceTimeBy(1000)
// WHEN another change in same group event happens
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
// THEN the forced validation remains for exactly 2 more seconds from now
// 1.500s from second event
advanceTimeBy(1500)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = true,
)
)
// 2.001s from the second event
advanceTimeBy(501)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
job.cancel()
}
@Test
fun `data switch - not in same group - uses new values`() =
testScope.runTest {
var latest: MobileConnectivityModel? = null
val job =
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = true,
isValidated = true,
)
)
connectionsRepository.setMobileConnectivity(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
assertThat(latest)
.isEqualTo(
MobileConnectivityModel(
isConnected = false,
isValidated = false,
)
)
job.cancel()
}
companion object { companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private val tableLogBuffer = private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock()) TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())

View File

@@ -273,6 +273,41 @@ class MobileIconViewModelTest : SysuiTestCase() {
job.cancel() 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 @Test
fun roaming() = fun roaming() =
testScope.runTest { testScope.runTest {