Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e43cc99e authored by Evan Laird's avatar Evan Laird
Browse files

[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
parent 2319065b
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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
+108 −0
Original line number Diff line number Diff line
/*
 * 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}"
    }
}
+39 −0
Original line number Diff line number Diff line
/*
 * 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() }
    }
}
+136 −0
Original line number Diff line number Diff line
/*
 * 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")
        }
    }
}
+15 −5
Original line number Diff line number Diff line
@@ -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,
Loading