[Sb refactor] CarrierConfigRepository

CarrierConfigRepository is meant to replace the old CarrierConfigTracker
class and be easier to add new keys to. It exposes the ability to get a
SystemUiCarrierConfig object which exposes tracked keys as individual
Flows to be consumed in the modern-arch way.

Test: CarrierConfigRepositoryTest
Test: SystemUiCarrierConfigTest
Test: MobileConnectionRepositoryTest
Bug: 238425913
Change-Id: Id3ae744f62aae526bdd8573151632814617c1bd7
This commit is contained in:
Evan Laird
2023-01-20 15:12:07 -05:00
parent 2319065bcf
commit e43cc99e30
10 changed files with 650 additions and 5 deletions

View File

@@ -25,6 +25,7 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.Airplane
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigCoreStartable
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -82,6 +83,11 @@ abstract class StatusBarPipelineModule {
@ClassKey(MobileUiAdapter::class)
abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
@Binds
@IntoMap
@ClassKey(CarrierConfigCoreStartable::class)
abstract fun bindCarrierConfigStartable(impl: CarrierConfigCoreStartable): CoreStartable
companion object {
@Provides
@SysUISingleton

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 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.os.PersistableBundle
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* Represents, for a given subscription ID, the set of keys about which SystemUI cares.
*
* Upon first creation, this config represents only the default configuration (see
* [android.telephony.CarrierConfigManager.getDefaultConfig]).
*
* Upon request (see
* [com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository]), an
* instance of this class may be created for a given subscription Id, and will default to
* representing the default carrier configuration. However, once a carrier config is received for
* this [subId], all fields will reflect those in the received config, using [PersistableBundle]'s
* default of false for any config that is not present in the override.
*
* To keep things relatively simple, this class defines a wrapper around each config key which
* exposes a StateFlow<Boolean> for each config we care about. It also tracks whether or not it is
* using the default config for logging purposes.
*
* NOTE to add new keys to be tracked:
* 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
* 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
* 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
* updated when a new carrier config comes down
*/
class SystemUiCarrierConfig
internal constructor(
val subId: Int,
defaultConfig: PersistableBundle,
) {
@VisibleForTesting
var isUsingDefault = true
private set
private val inflateSignalStrength =
BooleanCarrierConfig(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, defaultConfig)
/** Flow tracking the [KEY_INFLATE_SIGNAL_STRENGTH_BOOL] carrier config */
val shouldInflateSignalStrength: StateFlow<Boolean> = inflateSignalStrength.config
private val showOperatorName =
BooleanCarrierConfig(KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, defaultConfig)
/** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
private val trackedConfigs =
listOf(
inflateSignalStrength,
showOperatorName,
)
/** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
fun processNewCarrierConfig(config: PersistableBundle) {
isUsingDefault = false
trackedConfigs.forEach { it.update(config) }
}
/** For dumpsys, shortcut if we haven't overridden any keys */
fun toStringConsideringDefaults(): String {
return if (isUsingDefault) {
"using defaults"
} else {
trackedConfigs.joinToString { it.toString() }
}
}
override fun toString(): String = trackedConfigs.joinToString { it.toString() }
}
/** Extracts [key] from the carrier config, and stores it in a flow */
private class BooleanCarrierConfig(
val key: String,
defaultConfig: PersistableBundle,
) {
private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
val config = _configValue.asStateFlow()
fun update(config: PersistableBundle) {
_configValue.value = config.getBoolean(key)
}
override fun toString(): String {
return "$key=${config.value}"
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 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.repository
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* Core startable which configures the [CarrierConfigRepository] to listen for updates for the
* lifetime of the process
*/
class CarrierConfigCoreStartable
@Inject
constructor(
private val carrierConfigRepository: CarrierConfigRepository,
@Application private val scope: CoroutineScope,
) : CoreStartable {
override fun start() {
scope.launch { carrierConfigRepository.startObservingCarrierConfigUpdates() }
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2023 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.repository
import android.content.IntentFilter
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.util.SparseArray
import androidx.annotation.VisibleForTesting
import androidx.core.util.getOrElse
import androidx.core.util.isEmpty
import androidx.core.util.keyIterator
import com.android.systemui.Dumpable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
/**
* Meant to be the source of truth regarding CarrierConfigs. These are configuration objects defined
* on a per-subscriptionId basis, and do not trigger a device configuration event.
*
* Designed to supplant [com.android.systemui.util.CarrierConfigTracker].
*
* See [SystemUiCarrierConfig] for details on how to add carrier config keys to be tracked
*/
@SysUISingleton
class CarrierConfigRepository
@Inject
constructor(
broadcastDispatcher: BroadcastDispatcher,
private val carrierConfigManager: CarrierConfigManager,
dumpManager: DumpManager,
logger: ConnectivityPipelineLogger,
@Application scope: CoroutineScope,
) : Dumpable {
private var isListening = false
private val defaultConfig: PersistableBundle by lazy { CarrierConfigManager.getDefaultConfig() }
// Used for logging the default config in the dumpsys
private val defaultConfigForLogs: SystemUiCarrierConfig by lazy {
SystemUiCarrierConfig(-1, defaultConfig)
}
private val configs = SparseArray<SystemUiCarrierConfig>()
init {
dumpManager.registerNormalDumpable(this)
}
@VisibleForTesting
val carrierConfigStream: SharedFlow<Pair<Int, PersistableBundle>> =
broadcastDispatcher
.broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
intent,
_ ->
intent.getIntExtra(
CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID
)
}
.onEach { logger.logCarrierConfigChanged(it) }
.filter { SubscriptionManager.isValidSubscriptionId(it) }
.mapNotNull { subId ->
val config = carrierConfigManager.getConfigForSubId(subId)
config?.let { subId to it }
}
.shareIn(scope, SharingStarted.WhileSubscribed())
/**
* Start this repository observing broadcasts for **all** carrier configuration updates. Must be
* called in order to keep SystemUI in sync with [CarrierConfigManager].
*/
suspend fun startObservingCarrierConfigUpdates() {
isListening = true
carrierConfigStream.collect { updateCarrierConfig(it.first, it.second) }
}
/** Update or create the [SystemUiCarrierConfig] for subId with the override */
private fun updateCarrierConfig(subId: Int, config: PersistableBundle) {
val configToUpdate = getOrCreateConfigForSubId(subId)
configToUpdate.processNewCarrierConfig(config)
}
/** Gets a cached [SystemUiCarrierConfig], or creates a new one which will track the defaults */
fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig {
return configs.getOrElse(subId) {
val config = SystemUiCarrierConfig(subId, defaultConfig)
val carrierConfig = carrierConfigManager.getConfigForSubId(subId)
if (carrierConfig != null) config.processNewCarrierConfig(carrierConfig)
configs.put(subId, config)
config
}
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("isListening: $isListening")
if (configs.isEmpty()) {
pw.println("no carrier configs loaded")
} else {
pw.println("Carrier configs by subId")
configs.keyIterator().forEach {
pw.println(" subId=$it")
pw.println(" config=${configs.get(it).toStringConsideringDefaults()}")
}
// Finally, print the default config
pw.println("Default config:")
pw.println(" $defaultConfigForLogs")
}
}
}

View File

@@ -44,8 +44,10 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -63,7 +65,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
@@ -82,6 +84,7 @@ class MobileConnectionRepositoryImpl(
networkNameSeparator: String,
private val telephonyManager: TelephonyManager,
private val globalSettings: GlobalSettings,
systemUiCarrierConfig: SystemUiCarrierConfig,
broadcastDispatcher: BroadcastDispatcher,
globalMobileDataSettingChangedEvent: Flow<Unit>,
mobileMappingsProxy: MobileMappingsProxy,
@@ -216,10 +219,15 @@ class MobileConnectionRepositoryImpl(
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
// This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
// once it's wired up inside of [CarrierConfigTracker].
override val numberOfLevels: StateFlow<Int> =
flowOf(DEFAULT_NUM_LEVELS)
override val numberOfLevels =
systemUiCarrierConfig.shouldInflateSignalStrength
.map { shouldInflate ->
if (shouldInflate) {
DEFAULT_NUM_LEVELS + 1
} else {
DEFAULT_NUM_LEVELS
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
/** Produces whenever the mobile data setting changes for this subId */
@@ -300,6 +308,7 @@ class MobileConnectionRepositoryImpl(
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
private val globalSettings: GlobalSettings,
private val carrierConfigRepository: CarrierConfigRepository,
private val mobileMappingsProxy: MobileMappingsProxy,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@@ -318,6 +327,7 @@ class MobileConnectionRepositoryImpl(
networkNameSeparator,
telephonyManager.createForSubscriptionId(subId),
globalSettings,
carrierConfigRepository.getOrCreateConfigForSubId(subId),
broadcastDispatcher,
globalMobileDataSettingChangedEvent,
mobileMappingsProxy,

View File

@@ -231,6 +231,15 @@ constructor(
)
}
fun logCarrierConfigChanged(subId: Int) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{ int1 = subId },
{ "onCarrierConfigChanged: subId=$int1" },
)
}
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2023 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.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class SystemUiCarrierConfigTest : SysuiTestCase() {
lateinit var underTest: SystemUiCarrierConfig
@Before
fun setUp() {
underTest = SystemUiCarrierConfig(SUB_1_ID, createTestConfig())
}
@Test
fun `process new config - reflected by isUsingDefault`() {
// Starts out using the defaults
assertThat(underTest.isUsingDefault).isTrue()
// ANY new config means we're no longer tracking defaults
underTest.processNewCarrierConfig(createTestConfig())
assertThat(underTest.isUsingDefault).isFalse()
}
@Test
fun `process new config - updates all flows`() {
assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
underTest.processNewCarrierConfig(
configWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
)
)
assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
}
@Test
fun `process new config - defaults to false for config overrides`() {
// This case is only apparent when:
// 1. The default is true
// 2. The override config has no value for a given key
// In this case (per the old code) we would use the default value of false, despite there
// being no override key present in the override config
underTest =
SystemUiCarrierConfig(
SUB_1_ID,
configWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
)
)
assertThat(underTest.isUsingDefault).isTrue()
assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
// Process a new config with no keys
underTest.processNewCarrierConfig(PersistableBundle())
assertThat(underTest.isUsingDefault).isFalse()
assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
}
companion object {
private const val SUB_1_ID = 1
/**
* In order to keep us from having to update every place that might want to create a config,
* make sure to add new keys here
*/
fun createTestConfig() =
PersistableBundle().also {
it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
}
/** Override the default config with the given (key, value) pair */
fun configWithOverride(key: String, override: Boolean): PersistableBundle =
createTestConfig().also { it.putBoolean(key, override) }
/** Override any number of configs from the default */
fun configWithOverrides(vararg overrides: Pair<String, Boolean>) =
createTestConfig().also { config ->
overrides.forEach { (key, value) -> config.putBoolean(key, value) }
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2023 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.repository
import android.content.Intent
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class CarrierConfigRepositoryTest : SysuiTestCase() {
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
private lateinit var underTest: CarrierConfigRepository
private lateinit var mockitoSession: MockitoSession
private lateinit var carrierConfigCoreStartable: CarrierConfigCoreStartable
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var carrierConfigManager: CarrierConfigManager
@Mock private lateinit var dumpManager: DumpManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mockitoSession =
mockitoSession()
.initMocks(this)
.mockStatic(CarrierConfigManager::class.java)
.strictness(Strictness.LENIENT)
.startMocking()
whenever(CarrierConfigManager.getDefaultConfig()).thenReturn(DEFAULT_CONFIG)
whenever(carrierConfigManager.getConfigForSubId(anyInt())).thenAnswer { invocation ->
when (invocation.getArgument(0) as Int) {
1 -> CONFIG_1
2 -> CONFIG_2
else -> null
}
}
underTest =
CarrierConfigRepository(
fakeBroadcastDispatcher,
carrierConfigManager,
dumpManager,
logger,
testScope.backgroundScope,
)
carrierConfigCoreStartable =
CarrierConfigCoreStartable(underTest, testScope.backgroundScope)
}
@After
fun tearDown() {
mockitoSession.finishMocking()
}
@Test
fun `carrier config stream produces int-bundle pairs`() =
testScope.runTest {
var latest: Pair<Int, PersistableBundle>? = null
val job = underTest.carrierConfigStream.onEach { latest = it }.launchIn(this)
sendConfig(SUB_ID_1)
assertThat(latest).isEqualTo(Pair(SUB_ID_1, CONFIG_1))
sendConfig(SUB_ID_2)
assertThat(latest).isEqualTo(Pair(SUB_ID_2, CONFIG_2))
job.cancel()
}
@Test
fun `carrier config stream ignores invalid subscriptions`() =
testScope.runTest {
var latest: Pair<Int, PersistableBundle>? = null
val job = underTest.carrierConfigStream.onEach { latest = it }.launchIn(this)
sendConfig(INVALID_SUBSCRIPTION_ID)
assertThat(latest).isNull()
job.cancel()
}
@Test
fun `getOrCreateConfig - uses default config if no override`() {
val config = underTest.getOrCreateConfigForSubId(123)
assertThat(config.isUsingDefault).isTrue()
}
@Test
fun `getOrCreateConfig - uses override if exists`() {
val config = underTest.getOrCreateConfigForSubId(SUB_ID_1)
assertThat(config.isUsingDefault).isFalse()
}
@Test
fun `config - updates while config stream is collected`() =
testScope.runTest {
CONFIG_1.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
carrierConfigCoreStartable.start()
val config = underTest.getOrCreateConfigForSubId(SUB_ID_1)
assertThat(config.shouldInflateSignalStrength.value).isFalse()
CONFIG_1.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
sendConfig(SUB_ID_1)
assertThat(config.shouldInflateSignalStrength.value).isTrue()
}
private fun sendConfig(subId: Int) {
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(
context,
Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId)
)
}
}
companion object {
private const val SUB_ID_1 = 1
private const val SUB_ID_2 = 2
private val DEFAULT_CONFIG = createTestConfig()
private val CONFIG_1 = createTestConfig()
private val CONFIG_2 = createTestConfig()
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.Intent
import android.os.UserHandle
import android.provider.Settings
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.CellSignalStrengthCdma
import android.telephony.NetworkRegistrationInfo
import android.telephony.ServiceState
@@ -59,6 +60,9 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
@@ -100,6 +104,11 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy()
private val globalSettings = FakeSettings()
private val systemUiCarrierConfig =
SystemUiCarrierConfig(
SUB_1_ID,
createTestConfig(),
)
@Before
fun setUp() {
@@ -117,6 +126,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
SEP,
telephonyManager,
globalSettings,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
connectionsRepo.globalMobileDataSettingChangedEvent,
mobileMappings,
@@ -625,6 +635,29 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `number of levels - uses carrier config`() =
runBlocking(IMMEDIATE) {
var latest: Int? = null
val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
systemUiCarrierConfig.processNewCarrierConfig(
configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
)
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS + 1)
systemUiCarrierConfig.processNewCarrierConfig(
configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
)
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
job.cancel()
}
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())

View File

@@ -39,6 +39,7 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
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.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -80,6 +81,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var carrierConfigRepository: CarrierConfigRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@@ -119,6 +121,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
wifiRepository = FakeWifiRepository()
carrierConfigRepository =
CarrierConfigRepository(
fakeBroadcastDispatcher,
mock(),
mock(),
logger,
scope,
)
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
fakeBroadcastDispatcher,
@@ -129,6 +140,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = scope,
carrierConfigRepository = carrierConfigRepository,
)
carrierMergedFactory =
CarrierMergedConnectionRepository.Factory(