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

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

Merge "Create NrRepository" into main

parents 25796ffe 9179ad5f
Loading
Loading
Loading
Loading
+56 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settings.network.telephony

import android.content.Context
import android.os.Build
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
@@ -35,7 +36,8 @@ class CarrierConfigRepository(private val context: Context) {
    private enum class KeyType {
        BOOLEAN,
        INT,
        STRING
        INT_ARRAY,
        STRING,
    }

    interface CarrierConfigAccessor {
@@ -43,17 +45,20 @@ class CarrierConfigRepository(private val context: Context) {

        fun getInt(key: String): Int

        fun getIntArray(key: String): IntArray?

        fun getString(key: String): String?
    }

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

        override fun getBoolean(key: String): Boolean {
            checkBooleanKey(key)
            val value = cache[key]
            return if (value == null) {
                keysToRetrieve += key to KeyType.BOOLEAN
                addKeyToRetrieve(key, KeyType.BOOLEAN)
                DefaultConfig.getBoolean(key)
            } else {
                check(value is BooleanConfigValue) { "Boolean value type wrong" }
@@ -65,7 +70,7 @@ class CarrierConfigRepository(private val context: Context) {
            check(key.endsWith("_int")) { "Int key should ends with _int" }
            val value = cache[key]
            return if (value == null) {
                keysToRetrieve += key to KeyType.INT
                addKeyToRetrieve(key, KeyType.INT)
                DefaultConfig.getInt(key)
            } else {
                check(value is IntConfigValue) { "Int value type wrong" }
@@ -73,11 +78,23 @@ class CarrierConfigRepository(private val context: Context) {
            }
        }

        override fun getIntArray(key: String): IntArray? {
            checkIntArrayKey(key)
            val value = cache[key]
            return if (value == null) {
                addKeyToRetrieve(key, KeyType.INT_ARRAY)
                DefaultConfig.getIntArray(key)
            } else {
                check(value is IntArrayConfigValue) { "Int array 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
                addKeyToRetrieve(key, KeyType.STRING)
                DefaultConfig.getString(key)
            } else {
                check(value is StringConfigValue) { "String value type wrong" }
@@ -85,20 +102,35 @@ class CarrierConfigRepository(private val context: Context) {
            }
        }

        fun getKeysToRetrieve(): Map<String, KeyType> = keysToRetrieve
        private fun addKeyToRetrieve(key: String, type: KeyType) {
            if (keysToRetrieve.put(key, type) == null && Build.IS_DEBUGGABLE) {
                check(!isKeysToRetrieveFrozen) { "implement error for key $key" }
            }
        }

        /**
         * Gets the keys to retrieve.
         *
         * After this function is called, the keys to retrieve is frozen.
         */
        fun getAndFrozeKeysToRetrieve(): Map<String, KeyType> {
            isKeysToRetrieveFrozen = true
            return 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.
     * times, so it should be pure function without side effort. Please also make sure every key is
     * retrieved every time, for example, we need avoid expression shortcut.
     */
    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()
        val keysToRetrieve = accessor.getAndFrozeKeysToRetrieve()
        // If all keys found in the first pass, no need to collect again
        if (keysToRetrieve.isEmpty()) return result

@@ -113,6 +145,10 @@ class CarrierConfigRepository(private val context: Context) {
    /** Gets the configuration int for the given [subId] and [key]. */
    fun getInt(subId: Int, key: String): Int = transformConfig(subId) { getInt(key) }

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

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

@@ -122,6 +158,7 @@ class CarrierConfigRepository(private val context: Context) {
            when (type) {
                KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key))
                KeyType.INT -> this[key] = IntConfigValue(config.getInt(key))
                KeyType.INT_ARRAY -> this[key] = IntArrayConfigValue(config.getIntArray(key))
                KeyType.STRING -> this[key] = StringConfigValue(config.getString(key))
            }
        }
@@ -195,6 +232,10 @@ class CarrierConfigRepository(private val context: Context) {
            }
        }

        private fun checkIntArrayKey(key: String) {
            check(key.endsWith("_int_array")) { "Int array key should ends with _int_array" }
        }

        @VisibleForTesting
        fun setBooleanForTest(subId: Int, key: String, value: Boolean) {
            checkBooleanKey(key)
@@ -207,6 +248,12 @@ class CarrierConfigRepository(private val context: Context) {
            getPerSubCache(subId)[key] = IntConfigValue(value)
        }

        @VisibleForTesting
        fun setIntArrayForTest(subId: Int, key: String, value: IntArray?) {
            checkIntArrayKey(key)
            getPerSubCache(subId)[key] = IntArrayConfigValue(value)
        }

        @VisibleForTesting
        fun setStringForTest(subId: Int, key: String, value: String?) {
            check(key.endsWith("_string")) { "String key should ends with _string" }
@@ -221,6 +268,8 @@ private data class BooleanConfigValue(val value: Boolean) : ConfigValue

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

private class IntArrayConfigValue(val value: IntArray?) : ConfigValue

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

private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue>
+1 −12
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;

import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.ArrayUtils;
import com.android.settings.R;
import com.android.settings.network.ims.VolteQueryImsState;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -56,8 +55,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
    Preference mPreference;
    private PhoneCallStateTelephonyCallback mTelephonyCallback;
    private boolean mShow5gLimitedDialog;
    boolean mIsNrEnabledFromCarrierConfig;
    private boolean mHas5gCapability;
    private Integer mCallState;
    private final List<On4gLteUpdateListener> m4gLteListeners;

@@ -94,9 +91,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
        mShow5gLimitedDialog = carrierConfig.getBoolean(
                CarrierConfigManager.KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL);

        int[] nrAvailabilities = carrierConfig.getIntArray(
                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
        mIsNrEnabledFromCarrierConfig = !ArrayUtils.isEmpty(nrAvailabilities);
        return this;
    }

@@ -247,10 +241,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
            }
            mTelephonyManager.registerTelephonyCallback(
                    mContext.getMainExecutor(), mTelephonyCallback);

            final long supportedRadioBitmask = mTelephonyManager.getSupportedRadioAccessFamily();
            mHas5gCapability =
                    (supportedRadioBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0;
        }

        public void unregister() {
@@ -269,8 +259,7 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
    }

    private boolean isDialogNeeded() {
        Log.d(TAG, "Has5gCapability:" + mHas5gCapability);
        return mShow5gLimitedDialog && mHas5gCapability && mIsNrEnabledFromCarrierConfig;
        return mShow5gLimitedDialog && new NrRepository(mContext).isNrAvailable(mSubId);
    }

    private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) {
+25 −26
Original line number Diff line number Diff line
@@ -25,31 +25,28 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch

/**
 * Preference controller for "Voice over NR".
 */
class NrAdvancedCallingPreferenceController @JvmOverloads constructor(
/** Preference controller for "Voice over NR". */
class NrAdvancedCallingPreferenceController
@JvmOverloads
constructor(
    context: Context,
    key: String,
    private val voNrRepository: VoNrRepository = VoNrRepository(context),
    private val callStateRepository: CallStateRepository = CallStateRepository(context),
) : ComposePreferenceController(context, key) {
    private var subId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
    private var repository: VoNrRepository? = null
    private val searchItem = NrAdvancedCallingSearchItem(context)

    /** Initial this PreferenceController. */
    @JvmOverloads
    fun init(subId: Int, repository: VoNrRepository = VoNrRepository(mContext, subId)) {
    fun init(subId: Int) {
        this.subId = subId
        this.repository = repository
    }

    override fun getAvailabilityStatus() =
@@ -58,30 +55,32 @@ class NrAdvancedCallingPreferenceController @JvmOverloads constructor(
    @Composable
    override fun Content() {
        val summary = stringResource(R.string.nr_advanced_calling_summary)
        val isInCall by remember { callStateRepository.isInCallFlow() }
        val isInCall by
            remember { callStateRepository.isInCallFlow() }
                .collectAsStateWithLifecycle(initialValue = false)
        val isVoNrEnabled by
            remember { voNrRepository.isVoNrEnabledFlow(subId) }
                .collectAsStateWithLifecycle(initialValue = false)
        val isEnabled by remember {
            repository?.isVoNrEnabledFlow() ?: flowOf(false)
        }.collectAsStateWithLifecycle(initialValue = false)
        val coroutineScope = rememberCoroutineScope()
        SwitchPreference(object : SwitchPreferenceModel {
        SwitchPreference(
            object : SwitchPreferenceModel {
                override val title = stringResource(R.string.nr_advanced_calling_title)
                override val summary = { summary }
                override val changeable = { !isInCall }
            override val checked = { isEnabled }
                override val checked = { isVoNrEnabled }
                override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
                coroutineScope.launch {
                    repository?.setVoNrEnabled(newChecked)
                    coroutineScope.launch { voNrRepository.setVoNrEnabled(subId, newChecked) }
                }
            }
        })
        )
    }

    companion object {
        class NrAdvancedCallingSearchItem(private val context: Context) :
            MobileNetworkSettingsSearchItem {
            private val voNrRepository = VoNrRepository(context)

            fun isAvailable(subId: Int): Boolean = VoNrRepository(context, subId).isVoNrAvailable()
            fun isAvailable(subId: Int): Boolean = voNrRepository.isVoNrAvailable(subId)

            override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? {
                if (!isAvailable(subId)) return null
+50 −0
Original line number 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.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.util.Log

class NrRepository(private val context: Context) {
    private val carrierConfigRepository = CarrierConfigRepository(context)

    fun isNrAvailable(subId: Int): Boolean {
        if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability(subId)) {
            return false
        }
        val carrierNrAvailabilities =
            carrierConfigRepository.getIntArray(
                subId = subId,
                key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
            )
        return carrierNrAvailabilities?.isNotEmpty() ?: false
    }

    private fun has5gCapability(subId: Int): Boolean {
        val telephonyManager = context.telephonyManager(subId)
        return (telephonyManager.supportedRadioAccessFamily and
                TelephonyManager.NETWORK_TYPE_BITMASK_NR > 0)
            .also { Log.d(TAG, "[$subId] has5gCapability: $it") }
    }

    private companion object {
        private const val TAG = "NrRepository"
    }
}
+32 −33
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.settings.network.telephony
import android.content.Context
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@@ -29,40 +28,40 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext

class VoNrRepository(private val context: Context, private val subId: Int) {
    private val telephonyManager = context.telephonyManager(subId)
    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
class VoNrRepository(
    private val context: Context,
    private val nrRepository: NrRepository = NrRepository(context),
) {
    private val carrierConfigRepository = CarrierConfigRepository(context)

    fun isVoNrAvailable(): Boolean {
        if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability()) return false
        val carrierConfig = carrierConfigManager.safeGetConfig(
            keys = listOf(
                CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
                CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL,
                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
            ),
            subId = subId,
    fun isVoNrAvailable(subId: Int): Boolean {
        if (!nrRepository.isNrAvailable(subId)) return false
        data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean)
        val carrierConfig =
            carrierConfigRepository.transformConfig(subId) {
                Config(
                    isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL),
                    isVoNrSettingVisibility =
                        getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
                )
        return carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL) &&
            carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL) &&
            (carrierConfig.getIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)
                ?.isNotEmpty() ?: false)
            }
        return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
    }

    private fun has5gCapability() =
        ((telephonyManager.supportedRadioAccessFamily and
            TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0)
            .also { Log.d(TAG, "[$subId] has5gCapability: $it") }

    fun isVoNrEnabledFlow(): Flow<Boolean> = context.subscriptionsChangedFlow()
    fun isVoNrEnabledFlow(subId: Int): Flow<Boolean> {
        val telephonyManager = context.telephonyManager(subId)
        return context
            .subscriptionsChangedFlow()
            .map { telephonyManager.isVoNrEnabled }
            .conflate()
            .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
            .flowOn(Dispatchers.Default)
    }

    suspend fun setVoNrEnabled(enabled: Boolean) = withContext(Dispatchers.Default) {
    suspend fun setVoNrEnabled(subId: Int, enabled: Boolean) =
        withContext(Dispatchers.Default) {
            if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext
        val result = telephonyManager.setVoNrEnabled(enabled)
            val result = context.telephonyManager(subId).setVoNrEnabled(enabled)
            Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result")
        }

Loading