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

Commit 84c49b9f authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "RoamingPreferenceController" into main

* changes:
  Fix crash in RoamingPreferenceController
  New CarrierConfigRepository
parents 0a2d60a9 cfd401b0
Loading
Loading
Loading
Loading
+1 −5
Original line number Original line Diff line number Diff line
@@ -85,13 +85,9 @@
            android:summary="@string/auto_data_switch_summary"
            android:summary="@string/auto_data_switch_summary"
            settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/>
            settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/>


        <com.android.settingslib.RestrictedSwitchPreference
        <com.android.settings.spa.preference.ComposePreference
            android:key="button_roaming_key"
            android:key="button_roaming_key"
            android:title="@string/roaming"
            android:title="@string/roaming"
            android:persistent="false"
            android:summaryOn="@string/roaming_enable"
            android:summaryOff="@string/roaming_disable"
            settings:userRestriction="no_data_roaming"
            settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/>
            settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/>


        <Preference
        <Preference
+17 −20
Original line number Original line Diff line number Diff line
@@ -23,10 +23,10 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.settings.network.telephony.CarrierConfigRepository
import com.android.settings.network.telephony.SimSlotRepository
import com.android.settings.network.telephony.SimSlotRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
import com.android.settings.network.telephony.safeGetConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
@@ -39,7 +39,9 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch


@OptIn(ExperimentalCoroutinesApi::class)
@OptIn(ExperimentalCoroutinesApi::class)
class SimStatusDialogRepository @JvmOverloads constructor(
class SimStatusDialogRepository
@JvmOverloads
constructor(
    private val context: Context,
    private val context: Context,
    private val simSlotRepository: SimSlotRepository = SimSlotRepository(context),
    private val simSlotRepository: SimSlotRepository = SimSlotRepository(context),
    private val signalStrengthRepository: SignalStrengthRepository =
    private val signalStrengthRepository: SignalStrengthRepository =
@@ -48,7 +50,7 @@ class SimStatusDialogRepository @JvmOverloads constructor(
        ImsMmTelRepositoryImpl(context, subId)
        ImsMmTelRepositoryImpl(context, subId)
    },
    },
) {
) {
    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
    private val carrierConfigRepository = CarrierConfigRepository(context)


    data class SimStatusDialogInfo(
    data class SimStatusDialogInfo(
        val signalStrength: String? = null,
        val signalStrength: String? = null,
@@ -73,7 +75,8 @@ class SimStatusDialogRepository @JvmOverloads constructor(
    }
    }


    private fun simStatusDialogInfoBySlotFlow(simSlotIndex: Int): Flow<SimStatusDialogInfo> =
    private fun simStatusDialogInfoBySlotFlow(simSlotIndex: Int): Flow<SimStatusDialogInfo> =
        simSlotRepository.subIdInSimSlotFlow(simSlotIndex)
        simSlotRepository
            .subIdInSimSlotFlow(simSlotIndex)
            .flatMapLatest { subId ->
            .flatMapLatest { subId ->
                if (SubscriptionManager.isValidSubscriptionId(subId)) {
                if (SubscriptionManager.isValidSubscriptionId(subId)) {
                    simStatusDialogInfoFlow(subId)
                    simStatusDialogInfoFlow(subId)
@@ -99,22 +102,16 @@ class SimStatusDialogRepository @JvmOverloads constructor(
        }
        }


    private fun showUpFlow(subId: Int) = flow {
    private fun showUpFlow(subId: Int) = flow {
        val config = carrierConfigManager.safeGetConfig(
        val visibility =
            keys = listOf(
            carrierConfigRepository.transformConfig(subId) {
                CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
                SimStatusDialogVisibility(
                CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL,
                    signalStrengthShowUp =
            ),
                        getBoolean(
            subId = subId,
                            CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL),
        )
                    imsRegisteredShowUp =
        val visibility = SimStatusDialogVisibility(
                        getBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL),
            signalStrengthShowUp = config.getBoolean(
                CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
                true,  // by default we show the signal strength in sim status
            ),
            imsRegisteredShowUp = config.getBoolean(
                CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL
            ),
                )
                )
            }
        emit(visibility)
        emit(visibility)
    }
    }
}
}
+1 −0
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import androidx.core.os.persistableBundleOf
/**
/**
 * Gets the configuration values of the specified config keys applied.
 * Gets the configuration values of the specified config keys applied.
 */
 */
@Deprecated("Use CarrierConfigRepository instead")
fun CarrierConfigManager.safeGetConfig(
fun CarrierConfigManager.safeGetConfig(
    keys: List<String>,
    keys: List<String>,
    subId: Int = SubscriptionManager.getDefaultSubscriptionId(),
    subId: Int = SubscriptionManager.getDefaultSubscriptionId(),
+217 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.settings.network.telephony

import android.content.Context
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.util.Log
import androidx.annotation.VisibleForTesting
import java.util.concurrent.ConcurrentHashMap
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor

class CarrierConfigRepository(private val context: Context) {

    private val carrierConfigManager: CarrierConfigManager? =
        context.getSystemService(CarrierConfigManager::class.java)

    private enum class KeyType {
        BOOLEAN,
        INT,
        STRING
    }

    interface CarrierConfigAccessor {
        fun getBoolean(key: String): Boolean

        fun getInt(key: String): Int

        fun getString(key: String): String?
    }

    private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor {
        private val keysToRetrieve = mutableMapOf<String, KeyType>()

        override fun getBoolean(key: String): Boolean {
            check(key.endsWith("_bool")) { "Boolean key should ends with _bool" }
            val value = cache[key]
            return if (value == null) {
                keysToRetrieve += key to KeyType.BOOLEAN
                DefaultConfig.getBoolean(key)
            } else {
                check(value is BooleanConfigValue) { "Boolean value type wrong" }
                value.value
            }
        }

        override fun getInt(key: String): Int {
            check(key.endsWith("_int")) { "Int key should ends with _int" }
            val value = cache[key]
            return if (value == null) {
                keysToRetrieve += key to KeyType.INT
                DefaultConfig.getInt(key)
            } else {
                check(value is IntConfigValue) { "Int value type wrong" }
                value.value
            }
        }

        override fun getString(key: String): String? {
            check(key.endsWith("_string")) { "String key should ends with _string" }
            val value = cache[key]
            return if (value == null) {
                keysToRetrieve += key to KeyType.STRING
                DefaultConfig.getString(key)
            } else {
                check(value is StringConfigValue) { "String value type wrong" }
                value.value
            }
        }

        fun getKeysToRetrieve(): Map<String, KeyType> = keysToRetrieve
    }

    /**
     * Gets the configuration values for the given [subId].
     *
     * Configuration values could be accessed in [block]. Note: [block] could be called multiple
     * times, so it should be pure function without side effort.
     */
    fun <T> transformConfig(subId: Int, block: CarrierConfigAccessor.() -> T): T {
        val perSubCache = getPerSubCache(subId)
        val accessor = Accessor(perSubCache)
        val result = accessor.block()
        val keysToRetrieve = accessor.getKeysToRetrieve()
        // If all keys found in the first pass, no need to collect again
        if (keysToRetrieve.isEmpty()) return result

        perSubCache.update(subId, keysToRetrieve)

        return accessor.block()
    }

    /** Gets the configuration boolean for the given [subId] and [key]. */
    fun getBoolean(subId: Int, key: String): Boolean = transformConfig(subId) { getBoolean(key) }

    /** Gets the configuration int for the given [subId] and [key]. */
    fun getInt(subId: Int, key: String): Int = transformConfig(subId) { getInt(key) }

    /** Gets the configuration string for the given [subId] and [key]. */
    fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) }

    private fun ConfigCache.update(subId: Int, keysToRetrieve: Map<String, KeyType>) {
        val config = safeGetConfig(subId, keysToRetrieve.keys) ?: return
        for ((key, type) in keysToRetrieve) {
            when (type) {
                KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key))
                KeyType.INT -> this[key] = IntConfigValue(config.getInt(key))
                KeyType.STRING -> this[key] = StringConfigValue(config.getString(key))
            }
        }
    }

    /** Gets the configuration values of the specified config keys applied. */
    private fun safeGetConfig(subId: Int, keys: Collection<String>): PersistableBundle? {
        if (carrierConfigManager == null || !SubscriptionManager.isValidSubscriptionId(subId)) {
            return null
        }
        tryRegisterListener(context)
        return try {
            carrierConfigManager.getConfigForSubId(subId, *keys.toTypedArray())
        } catch (e: Exception) {
            Log.e(TAG, "safeGetConfig: exception", e)
            // The CarrierConfigLoader (the service implemented the CarrierConfigManager) hasn't
            // been initialized yet. This may occurs during very early phase of phone booting up
            // or when Phone process has been restarted.
            // Settings should not assume Carrier config loader (and any other system services
            // as well) are always available. If not available, use default value instead.
            null
        }
    }

    companion object {
        private const val TAG = "CarrierConfigRepository"

        private val DefaultConfig = CarrierConfigManager.getDefaultConfig()

        /** Cache of config values for each subscription. */
        private val Cache = ConcurrentHashMap<Int, ConfigCache>()

        private fun getPerSubCache(subId: Int) =
            Cache.computeIfAbsent(subId) { ConcurrentHashMap() }

        /** To make sure the registerCarrierConfigChangeListener is only called once. */
        private val ListenerRegistered = atomic(false)

        private fun tryRegisterListener(context: Context) {
            if (ListenerRegistered.compareAndSet(expect = false, update = true)) {
                val carrierConfigManager =
                    context.applicationContext.getSystemService(CarrierConfigManager::class.java)
                if (carrierConfigManager != null) {
                    carrierConfigManager.registerCarrierConfigChangeListener()
                } else {
                    ListenerRegistered.getAndSet(false)
                }
            }
        }

        private fun CarrierConfigManager.registerCarrierConfigChangeListener() {
            val executor = Dispatchers.Default.asExecutor()
            registerCarrierConfigChangeListener(executor) { _, subId, _, _ ->
                Log.d(TAG, "[$subId] onCarrierConfigChanged")
                Cache.remove(subId)
            }
        }

        @VisibleForTesting
        fun resetForTest() {
            Cache.clear()
            ListenerRegistered.getAndSet(false)
        }

        @VisibleForTesting
        fun setBooleanForTest(subId: Int, key: String, value: Boolean) {
            check(key.endsWith("_bool")) { "Boolean key should ends with _bool" }
            getPerSubCache(subId)[key] = BooleanConfigValue(value)
        }

        @VisibleForTesting
        fun setIntForTest(subId: Int, key: String, value: Int) {
            check(key.endsWith("_int")) { "Int key should ends with _int" }
            getPerSubCache(subId)[key] = IntConfigValue(value)
        }

        @VisibleForTesting
        fun setStringForTest(subId: Int, key: String, value: String) {
            check(key.endsWith("_string")) { "String key should ends with _string" }
            getPerSubCache(subId)[key] = StringConfigValue(value)
        }
    }
}

private sealed interface ConfigValue

private data class BooleanConfigValue(val value: Boolean) : ConfigValue

private data class IntConfigValue(val value: Int) : ConfigValue

private data class StringConfigValue(val value: String?) : ConfigValue

private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue>
+12 −0
Original line number Original line Diff line number Diff line
@@ -118,6 +118,18 @@ class MobileDataRepository(
        }
        }
    }
    }


    /** Creates an instance of a cold Flow for whether data roaming is enabled of given [subId]. */
    fun isDataRoamingEnabledFlow(subId: Int): Flow<Boolean> {
        if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
        val telephonyManager = context.telephonyManager(subId)
        return mobileSettingsGlobalChangedFlow(Settings.Global.DATA_ROAMING, subId)
            .map { telephonyManager.isDataRoamingEnabled }
            .distinctUntilChanged()
            .conflate()
            .onEach { Log.d(TAG, "[$subId] isDataRoamingEnabledFlow: $it") }
            .flowOn(Dispatchers.Default)
    }

    private companion object {
    private companion object {
        private const val TAG = "MobileDataRepository"
        private const val TAG = "MobileDataRepository"
    }
    }
Loading