summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Laird <evanlaird@google.com> 2023-01-20 15:12:07 -0500
committer Evan Laird <evanlaird@google.com> 2023-02-01 12:21:18 -0500
commite43cc99e308e6a19599c010a057336c4334120c1 (patch)
tree07963e10325b518fda4c85fabb594e725ed08b0d
parent2319065bcf3978e93d6538645c9206d1d296677d (diff)
[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
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigCoreStartable.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt120
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt172
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt12
10 files changed, 650 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 0993ab3701f6..5f3b0dcb1654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
new file mode 100644
index 000000000000..8c82fbac90b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -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}"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigCoreStartable.kt
new file mode 100644
index 000000000000..af58999d9ddf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigCoreStartable.kt
@@ -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() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
new file mode 100644
index 000000000000..5769f90ab6c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -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")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 3f2ce4000ff1..266430f13e33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -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,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 7c7ffaf5c617..a25e52ba845f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -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"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
new file mode 100644
index 000000000000..63cb30ca4a33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -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) }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
new file mode 100644
index 000000000000..521c67f20cfd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
@@ -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()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index d6b8c0dbc59d..b8dda780755e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -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())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index db8172a5cacf..f9567fc1eafc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -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(