diff options
| author | 2023-01-20 15:12:07 -0500 | |
|---|---|---|
| committer | 2023-02-01 12:21:18 -0500 | |
| commit | e43cc99e308e6a19599c010a057336c4334120c1 (patch) | |
| tree | 07963e10325b518fda4c85fabb594e725ed08b0d | |
| parent | 2319065bcf3978e93d6538645c9206d1d296677d (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
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( |