[SB Refactor] Migrate alwaysShowDataRatIcon to the new pipeline.

Bug: 238425913
Test: verified RAT icon was displayed when config was true
Test: atest MobileIconViewModelTest MobileConnectionsRepositoryTest
Change-Id: I5882dbe60d9e465c8cc15c84594c47769714aa7e
This commit is contained in:
Caitlin Shkuratov
2022-12-13 21:35:21 +00:00
parent 80d7be59bc
commit 3000d2b382
15 changed files with 278 additions and 23 deletions

View File

@@ -251,5 +251,22 @@ public class MobileMappings {
}
return config;
}
/**
* Returns true if this config and the other config are semantically equal.
*
* Does not override isEquals because existing clients may be relying on the currently
* defined equals behavior.
*/
public boolean areEqual(Config other) {
return showAtLeast3G == other.showAtLeast3G
&& show4gFor3g == other.show4gFor3g
&& alwaysShowCdmaRssi == other.alwaysShowCdmaRssi
&& show4gForLte == other.show4gForLte
&& show4glteForLte == other.show4glteForLte
&& hideLtePlus == other.hideLtePlus
&& hspaDataDistinguishable == other.hspaDataDistinguishable
&& alwaysShowDataRatIcon == other.alwaysShowDataRatIcon;
}
}
}

View File

@@ -17,8 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.provider.Settings
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow
@@ -47,6 +50,18 @@ interface MobileConnectionsRepository {
/** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
val globalMobileDataSettingChangedEvent: Flow<Unit>
/**
* [Config] is an object that tracks relevant configuration flags for a given subscription ID.
* In the case of [MobileMappings], it's hard-coded to check the default data subscription's
* config, so this will apply to every icon that we care about.
*
* Relevant bits in the config are things like
* [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
*
* This flow will produce whenever the default data subscription or the carrier config changes.
*/
val defaultDataSubRatConfig: StateFlow<Config>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>

View File

@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
@@ -123,6 +124,15 @@ constructor(
realRepository.activeMobileDataSubscriptionId.value
)
override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
activeRepo
.flatMapLatest { it.defaultDataSubRatConfig }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
realRepository.defaultDataSubRatConfig.value
)
override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
activeRepo.flatMapLatest { it.defaultMobileIconMapping }

View File

@@ -106,7 +106,8 @@ constructor(
)
/** Demo mode doesn't currently support modifications to the mobile mappings */
val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config.readConfig(context))
override val defaultDataSubRatConfig =
MutableStateFlow(MobileMappings.Config.readConfig(context))
override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)

View File

@@ -37,7 +37,6 @@ import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -152,17 +151,7 @@ constructor(
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
)
/**
* [Config] is an object that tracks relevant configuration flags for a given subscription ID.
* In the case of [MobileMappings], it's hard-coded to check the default data subscription's
* config, so this will apply to every icon that we care about.
*
* Relevant bits in the config are things like
* [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
*
* This flow will produce whenever the default data subscription or the carrier config changes.
*/
private val defaultDataSubRatConfig: StateFlow<Config> =
override val defaultDataSubRatConfig: StateFlow<Config> =
merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) }
.stateIn(

View File

@@ -45,6 +45,9 @@ interface MobileIconInteractor {
/** Observable for the data enabled state of this connection */
val isDataEnabled: StateFlow<Boolean>
/** True if the RAT icon should always be displayed and false otherwise. */
val alwaysShowDataRatIcon: StateFlow<Boolean>
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<MobileIconGroup>
@@ -64,6 +67,7 @@ interface MobileIconInteractor {
class MobileIconInteractorImpl(
@Application scope: CoroutineScope,
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
override val alwaysShowDataRatIcon: StateFlow<Boolean>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
override val isDefaultConnectionFailed: StateFlow<Boolean>,

View File

@@ -55,6 +55,8 @@ interface MobileIconsInteractor {
val filteredSubscriptions: Flow<List<SubscriptionModel>>
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
/** True if the RAT icon should always be displayed and false otherwise. */
val alwaysShowDataRatIcon: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
@@ -158,6 +160,11 @@ constructor(
initialValue = mapOf()
)
override val alwaysShowDataRatIcon: StateFlow<Boolean> =
mobileConnectionsRepo.defaultDataSubRatConfig
.mapLatest { it.alwaysShowDataRatIcon }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
@@ -188,6 +195,7 @@ constructor(
MobileIconInteractorImpl(
scope,
activeDataConnectionHasDataEnabled,
alwaysShowDataRatIcon,
defaultMobileIconMapping,
defaultMobileIconGroup,
isDefaultConnectionFailed,

View File

@@ -71,15 +71,19 @@ constructor(
iconInteractor.isDataConnected,
iconInteractor.isDataEnabled,
iconInteractor.isDefaultConnectionFailed,
) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
if (!dataConnected || !dataEnabled || failedConnection) {
null
} else {
val desc =
if (networkTypeIconGroup.dataContentDescription != 0)
ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
else null
Icon.Resource(networkTypeIconGroup.dataType, desc)
iconInteractor.alwaysShowDataRatIcon,
) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
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
else -> icon
}
}

View File

@@ -20,6 +20,7 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -68,6 +69,8 @@ class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
override val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config())
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping

View File

@@ -23,6 +23,7 @@ import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.provider.Settings
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
@@ -30,6 +31,8 @@ import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.R
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -50,6 +53,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
@@ -63,6 +67,7 @@ import org.mockito.MockitoAnnotations
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@@ -84,7 +89,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
}
val connectionFactory: MobileConnectionRepositoryImpl.Factory =
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
context = context,
telephonyManager = telephonyManager,
@@ -385,6 +390,92 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun config_initiallyFromContext() =
runBlocking(IMMEDIATE) {
overrideResource(R.bool.config_showMin3G, true)
val configFromContext = MobileMappings.Config.readConfig(context)
assertThat(configFromContext.showAtLeast3G).isTrue()
// The initial value will be fetched when the repo is created, so we need to override
// the resources and then re-create the repo.
underTest =
MobileConnectionsRepositoryImpl(
connectivityManager,
subscriptionManager,
telephonyManager,
logger,
mobileMappings,
fakeBroadcastDispatcher,
globalSettings,
context,
IMMEDIATE,
scope,
connectionFactory,
)
var latest: MobileMappings.Config? = null
val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
assertTrue(latest!!.areEqual(configFromContext))
assertTrue(latest!!.showAtLeast3G)
job.cancel()
}
@Test
fun config_subIdChangeEvent_updated() =
runBlocking(IMMEDIATE) {
var latest: MobileMappings.Config? = null
val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
assertThat(latest!!.showAtLeast3G).isFalse()
overrideResource(R.bool.config_showMin3G, true)
val configFromContext = MobileMappings.Config.readConfig(context)
assertThat(configFromContext.showAtLeast3G).isTrue()
// WHEN the change event is fired
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(
context,
Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
.putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
)
}
// THEN the config is updated
assertTrue(latest!!.areEqual(configFromContext))
assertTrue(latest!!.showAtLeast3G)
job.cancel()
}
@Test
fun config_carrierConfigChangeEvent_updated() =
runBlocking(IMMEDIATE) {
var latest: MobileMappings.Config? = null
val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
assertThat(latest!!.showAtLeast3G).isFalse()
overrideResource(R.bool.config_showMin3G, true)
val configFromContext = MobileMappings.Config.readConfig(context)
assertThat(configFromContext.showAtLeast3G).isTrue()
// WHEN the change event is fired
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(
context,
Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
)
}
// THEN the config is updated
assertThat(latest!!.areEqual(configFromContext)).isTrue()
assertThat(latest!!.showAtLeast3G).isTrue()
job.cancel()
}
private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)

View File

@@ -22,6 +22,8 @@ import com.android.settingslib.mobile.TelephonyIcons
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor {
override val alwaysShowDataRatIcon = MutableStateFlow(false)
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup

View File

@@ -54,6 +54,8 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
override val alwaysShowDataRatIcon = MutableStateFlow(false)
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping

View File

@@ -59,6 +59,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
MobileIconInteractorImpl(
scope,
mobileIconsInteractor.activeDataConnectionHasDataEnabled,
mobileIconsInteractor.alwaysShowDataRatIcon,
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.isDefaultConnectionFailed,
@@ -222,6 +223,21 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel()
}
@Test
fun alwaysShowDataRatIcon_matchesParent() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
mobileIconsInteractor.alwaysShowDataRatIcon.value = true
assertThat(latest).isTrue()
mobileIconsInteractor.alwaysShowDataRatIcon.value = false
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun test_isDefaultDataEnabled_matchesParent() =
runBlocking(IMMEDIATE) {

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -255,6 +256,38 @@ class MobileIconsInteractorTest : SysuiTestCase() {
job.cancel()
}
@Test
fun alwaysShowDataRatIcon_configHasTrue() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
val config = MobileMappings.Config()
config.alwaysShowDataRatIcon = true
connectionsRepository.defaultDataSubRatConfig.value = config
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun alwaysShowDataRatIcon_configHasFalse() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
val config = MobileMappings.Config()
config.alwaysShowDataRatIcon = false
connectionsRepository.defaultDataSubRatConfig.value = config
yield()
assertThat(latest).isFalse()
job.cancel()
}
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -174,6 +174,66 @@ class MobileIconViewModelTest : SysuiTestCase() {
job.cancel()
}
@Test
fun networkType_alwaysShow_shownEvenWhenDisabled() =
runBlocking(IMMEDIATE) {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true)
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 networkType_alwaysShow_shownEvenWhenDisconnected() =
runBlocking(IMMEDIATE) {
interactor.setIconGroup(THREE_G)
interactor.isDataConnected.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 networkType_alwaysShow_shownEvenWhenFailedConnection() =
runBlocking(IMMEDIATE) {
interactor.setIconGroup(THREE_G)
interactor.setIsFailedConnection(true)
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()
}
/** Convenience constructor for these tests */
private fun defaultSignal(
level: Int = 1,