diff --git a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt index 88525402e34..99683a8671a 100644 --- a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt +++ b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt @@ -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() + 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 = 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 { + 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 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 diff --git a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java index 1a71e5aad45..d1988c4a3b7 100644 --- a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java +++ b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java @@ -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 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) { diff --git a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt index 0d8766e4df0..b79e818c20e 100644 --- a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt +++ b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt @@ -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 callStateRepository : CallStateRepository = CallStateRepository(context), + 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() } - .collectAsStateWithLifecycle(initialValue = false) - val isEnabled by remember { - repository?.isVoNrEnabledFlow() ?: flowOf(false) - }.collectAsStateWithLifecycle(initialValue = false) + val isInCall by + remember { callStateRepository.isInCallFlow() } + .collectAsStateWithLifecycle(initialValue = false) + val isVoNrEnabled by + remember { voNrRepository.isVoNrEnabledFlow(subId) } + .collectAsStateWithLifecycle(initialValue = false) val coroutineScope = rememberCoroutineScope() - 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 onCheckedChange: (Boolean) -> Unit = { newChecked -> - coroutineScope.launch { - repository?.setVoNrEnabled(newChecked) + SwitchPreference( + object : SwitchPreferenceModel { + override val title = stringResource(R.string.nr_advanced_calling_title) + override val summary = { summary } + override val changeable = { !isInCall } + override val checked = { isVoNrEnabled } + override val onCheckedChange: (Boolean) -> Unit = { 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 diff --git a/src/com/android/settings/network/telephony/NrRepository.kt b/src/com/android/settings/network/telephony/NrRepository.kt new file mode 100644 index 00000000000..e6247fd2655 --- /dev/null +++ b/src/com/android/settings/network/telephony/NrRepository.kt @@ -0,0 +1,50 @@ +/* + * 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" + } +} diff --git a/src/com/android/settings/network/telephony/VoNrRepository.kt b/src/com/android/settings/network/telephony/VoNrRepository.kt index 35af2844609..635c57223be 100644 --- a/src/com/android/settings/network/telephony/VoNrRepository.kt +++ b/src/com/android/settings/network/telephony/VoNrRepository.kt @@ -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,43 +28,43 @@ 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, - ) - 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) + 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.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 = context.subscriptionsChangedFlow() - .map { telephonyManager.isVoNrEnabled } - .conflate() - .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") } - .flowOn(Dispatchers.Default) - - suspend fun setVoNrEnabled(enabled: Boolean) = withContext(Dispatchers.Default) { - if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext - val result = telephonyManager.setVoNrEnabled(enabled) - Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result") + fun isVoNrEnabledFlow(subId: Int): Flow { + 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(subId: Int, enabled: Boolean) = + withContext(Dispatchers.Default) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext + val result = context.telephonyManager(subId).setVoNrEnabled(enabled) + Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result") + } + private companion object { private const val TAG = "VoNrRepository" } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt index 8c54751da35..12bbcaf49e5 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt @@ -77,6 +77,19 @@ class CarrierConfigRepositoryTest { assertThat(value).isEqualTo(99) } + @Test + fun getIntArray_returnValue() { + val key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY + mockCarrierConfigManager.stub { + on { getConfigForSubId(any(), eq(key)) } doReturn + persistableBundleOf(key to intArrayOf(99)) + } + + val value = repository.getIntArray(SUB_ID, key)!!.toList() + + assertThat(value).containsExactly(99) + } + @Test fun getString_returnValue() { val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING @@ -104,7 +117,8 @@ class CarrierConfigRepositoryTest { assertThat(carrierName) .isEqualTo( CarrierConfigManager.getDefaultConfig() - .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)) + .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT) + ) } @Test diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt index 418a00b057f..c8fc977b500 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt @@ -41,91 +41,77 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class NrAdvancedCallingPreferenceControllerTest { - @get:Rule - val composeTestRule = createComposeRule() + @get:Rule val composeTestRule = createComposeRule() private val context: Context = spy(ApplicationProvider.getApplicationContext()) {} - private val callStateRepository = mock { - on { isInCallFlow() } doReturn flowOf(false) - } + private val callStateRepository = + mock { on { isInCallFlow() } doReturn flowOf(false) } - private val voNrRepository = mock() + private val voNrRepository = + mock { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) } - private val controller = NrAdvancedCallingPreferenceController( - context = context, - key = TEST_KEY, - callStateRepository = callStateRepository, - ).apply { init(SUB_ID, voNrRepository) } + private val controller = + NrAdvancedCallingPreferenceController( + context = context, + key = TEST_KEY, + voNrRepository = voNrRepository, + callStateRepository = callStateRepository, + ) + .apply { init(SUB_ID) } @Test fun isChecked_voNrEnabled_on() { - voNrRepository.stub { - on { isVoNrEnabledFlow() } doReturn flowOf(true) - } + voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsOn() } @Test fun isChecked_voNrDisabled_off() { - voNrRepository.stub { - on { isVoNrEnabledFlow() } doReturn flowOf(false) - } + voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsOff() } @Test - fun isEnabled_notInCall_enabled() { - callStateRepository.stub { - on { isInCallFlow() } doReturn flowOf(false) - } + fun isChangeable_notInCall_changeable() { + callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(false) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsEnabled() } @Test - fun isEnabled_inCall_notEnabled() { - callStateRepository.stub { - on { isInCallFlow() } doReturn flowOf(true) - } + fun isChangeable_inCall_notChangeable() { + callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(true) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } - composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) + composeTestRule + .onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) .assertIsNotEnabled() } @Test fun onClick_setVoNrEnabled(): Unit = runBlocking { - voNrRepository.stub { - on { isVoNrEnabledFlow() } doReturn flowOf(false) - } + voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) } - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } composeTestRule.onRoot().performClick() - verify(voNrRepository).setVoNrEnabled(true) + verify(voNrRepository).setVoNrEnabled(SUB_ID, true) } private companion object { diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt new file mode 100644 index 00000000000..2df2a0fae59 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt @@ -0,0 +1,109 @@ +/* + * 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.TelephonyManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class NrRepositoryTest { + private val mockTelephonyManager = + mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR + } + + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + private val repository = NrRepository(context) + + @Before + fun setUp() { + CarrierConfigRepository.resetForTest() + } + + @Test + fun isNrAvailable_deviceNoNr_returnFalse() { + mockTelephonyManager.stub { + on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE + } + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isFalse() + } + + @Test + fun isNrAvailable_carrierConfigNrIsEmpty_returnFalse() { + CarrierConfigRepository.setIntArrayForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + value = intArrayOf(), + ) + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isFalse() + } + + @Test + fun isNrAvailable_carrierConfigNrIsNull_returnFalse() { + CarrierConfigRepository.setIntArrayForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + value = null, + ) + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isFalse() + } + + @Test + fun isNrAvailable_allEnabled_returnTrue() { + mockTelephonyManager.stub { + on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR + } + CarrierConfigRepository.setIntArrayForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, + value = intArrayOf(1, 2), + ) + + val available = repository.isNrAvailable(SUB_ID) + + assertThat(available).isTrue() + } + + private companion object { + const val SUB_ID = 10 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt index 9c20afe5e85..90d0aa56d77 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt @@ -19,17 +19,15 @@ package com.android.settings.network.telephony import android.content.Context import android.telephony.CarrierConfigManager import android.telephony.TelephonyManager -import androidx.core.os.persistableBundleOf import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.anyVararg import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.stub @@ -38,127 +36,107 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class VoNrRepositoryTest { - private val mockTelephonyManager = mock { - on { createForSubscriptionId(SUB_ID) } doReturn mock - on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR + private val mockTelephonyManager = + mock { on { createForSubscriptionId(SUB_ID) } doReturn mock } + + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + private val mockNrRepository = mock { on { isNrAvailable(SUB_ID) } doReturn true } + + private val repository = VoNrRepository(context, mockNrRepository) + + @Before + fun setUp() { + CarrierConfigRepository.resetForTest() + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL, + value = true, + ) + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, + value = true, + ) } - private val carrierConfig = persistableBundleOf( - CarrierConfigManager.KEY_VONR_ENABLED_BOOL to true, - CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL to true, - CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY to intArrayOf(1, 2), - ) - - private val mockCarrierConfigManager = mock { - on { getConfigForSubId(eq(SUB_ID), anyVararg()) } doReturn carrierConfig - } - - private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager - on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager - } - - private val repository = VoNrRepository(context, SUB_ID) - @Test fun isVoNrAvailable_visibleDisable_returnFalse() { - carrierConfig.apply { - putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, false) - } + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, + value = false, + ) - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isFalse() } @Test fun isVoNrAvailable_voNrDisabled_returnFalse() { - carrierConfig.apply { - putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, false) - } + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL, + value = false, + ) - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isFalse() } @Test fun isVoNrAvailable_allEnabled_returnTrue() { - mockTelephonyManager.stub { - on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR - } - carrierConfig.apply { - putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, true) - putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, true) - putIntArray( - CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, - intArrayOf(1, 2), - ) - } + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL, + value = true, + ) + CarrierConfigRepository.setBooleanForTest( + subId = SUB_ID, + key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, + value = true, + ) - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isTrue() } @Test - fun isVoNrAvailable_deviceNoNr_returnFalse() { - mockTelephonyManager.stub { - on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE - } + fun isVoNrAvailable_noNr_returnFalse() { + mockNrRepository.stub { on { isNrAvailable(SUB_ID) } doReturn false } - val available = repository.isVoNrAvailable() - - assertThat(available).isFalse() - } - - @Test - fun isVoNrAvailable_carrierNoNr_returnFalse() { - carrierConfig.apply { - putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, intArrayOf()) - } - - val available = repository.isVoNrAvailable() - - assertThat(available).isFalse() - } - - @Test - fun isVoNrAvailable_carrierConfigNrIsNull_returnFalse() { - carrierConfig.apply { - putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, null) - } - - val available = repository.isVoNrAvailable() + val available = repository.isVoNrAvailable(SUB_ID) assertThat(available).isFalse() } @Test fun isVoNrEnabledFlow_voNrDisabled() = runBlocking { - mockTelephonyManager.stub { - on { isVoNrEnabled } doReturn false - } + mockTelephonyManager.stub { on { isVoNrEnabled } doReturn false } - val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() + val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(isVoNrEnabled).isFalse() } @Test fun isVoNrEnabledFlow_voNrEnabled() = runBlocking { - mockTelephonyManager.stub { - on { isVoNrEnabled } doReturn true - } + mockTelephonyManager.stub { on { isVoNrEnabled } doReturn true } - val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() + val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(isVoNrEnabled).isTrue() } @Test fun setVoNrEnabled(): Unit = runBlocking { - repository.setVoNrEnabled(true) + repository.setVoNrEnabled(SUB_ID, true) verify(mockTelephonyManager).setVoNrEnabled(true) }