Create NrRepository

To unify the isNrAvailable logic.

Bug: 358000881
Flag: EXEMPT refactor
Test: manual - on mobile settings
Test: atest NrRepositoryTest
Change-Id: I60fed4be10f0cc7b8b1a942abd598e82c53d1473
This commit is contained in:
Chaohui Wang
2024-09-05 15:30:20 +08:00
parent 5f3872baad
commit 9179ad5fc2
9 changed files with 379 additions and 206 deletions

View File

@@ -17,6 +17,7 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.os.Build
import android.os.PersistableBundle import android.os.PersistableBundle
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
@@ -35,7 +36,8 @@ class CarrierConfigRepository(private val context: Context) {
private enum class KeyType { private enum class KeyType {
BOOLEAN, BOOLEAN,
INT, INT,
STRING INT_ARRAY,
STRING,
} }
interface CarrierConfigAccessor { interface CarrierConfigAccessor {
@@ -43,17 +45,20 @@ class CarrierConfigRepository(private val context: Context) {
fun getInt(key: String): Int fun getInt(key: String): Int
fun getIntArray(key: String): IntArray?
fun getString(key: String): String? fun getString(key: String): String?
} }
private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor { private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor {
private val keysToRetrieve = mutableMapOf<String, KeyType>() private val keysToRetrieve = mutableMapOf<String, KeyType>()
private var isKeysToRetrieveFrozen = false
override fun getBoolean(key: String): Boolean { override fun getBoolean(key: String): Boolean {
checkBooleanKey(key) checkBooleanKey(key)
val value = cache[key] val value = cache[key]
return if (value == null) { return if (value == null) {
keysToRetrieve += key to KeyType.BOOLEAN addKeyToRetrieve(key, KeyType.BOOLEAN)
DefaultConfig.getBoolean(key) DefaultConfig.getBoolean(key)
} else { } else {
check(value is BooleanConfigValue) { "Boolean value type wrong" } 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" } check(key.endsWith("_int")) { "Int key should ends with _int" }
val value = cache[key] val value = cache[key]
return if (value == null) { return if (value == null) {
keysToRetrieve += key to KeyType.INT addKeyToRetrieve(key, KeyType.INT)
DefaultConfig.getInt(key) DefaultConfig.getInt(key)
} else { } else {
check(value is IntConfigValue) { "Int value type wrong" } 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? { override fun getString(key: String): String? {
check(key.endsWith("_string")) { "String key should ends with _string" } check(key.endsWith("_string")) { "String key should ends with _string" }
val value = cache[key] val value = cache[key]
return if (value == null) { return if (value == null) {
keysToRetrieve += key to KeyType.STRING addKeyToRetrieve(key, KeyType.STRING)
DefaultConfig.getString(key) DefaultConfig.getString(key)
} else { } else {
check(value is StringConfigValue) { "String value type wrong" } 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]. * Gets the configuration values for the given [subId].
* *
* Configuration values could be accessed in [block]. Note: [block] could be called multiple * 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 { fun <T> transformConfig(subId: Int, block: CarrierConfigAccessor.() -> T): T {
val perSubCache = getPerSubCache(subId) val perSubCache = getPerSubCache(subId)
val accessor = Accessor(perSubCache) val accessor = Accessor(perSubCache)
val result = accessor.block() 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 all keys found in the first pass, no need to collect again
if (keysToRetrieve.isEmpty()) return result 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]. */ /** Gets the configuration int for the given [subId] and [key]. */
fun getInt(subId: Int, key: String): Int = transformConfig(subId) { getInt(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]. */ /** Gets the configuration string for the given [subId] and [key]. */
fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) } fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) }
@@ -122,6 +158,7 @@ class CarrierConfigRepository(private val context: Context) {
when (type) { when (type) {
KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key)) KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key))
KeyType.INT -> this[key] = IntConfigValue(config.getInt(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)) 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 @VisibleForTesting
fun setBooleanForTest(subId: Int, key: String, value: Boolean) { fun setBooleanForTest(subId: Int, key: String, value: Boolean) {
checkBooleanKey(key) checkBooleanKey(key)
@@ -207,6 +248,12 @@ class CarrierConfigRepository(private val context: Context) {
getPerSubCache(subId)[key] = IntConfigValue(value) getPerSubCache(subId)[key] = IntConfigValue(value)
} }
@VisibleForTesting
fun setIntArrayForTest(subId: Int, key: String, value: IntArray?) {
checkIntArrayKey(key)
getPerSubCache(subId)[key] = IntArrayConfigValue(value)
}
@VisibleForTesting @VisibleForTesting
fun setStringForTest(subId: Int, key: String, value: String?) { fun setStringForTest(subId: Int, key: String, value: String?) {
check(key.endsWith("_string")) { "String key should ends with _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 data class IntConfigValue(val value: Int) : ConfigValue
private class IntArrayConfigValue(val value: IntArray?) : ConfigValue
private data class StringConfigValue(val value: String?) : ConfigValue private data class StringConfigValue(val value: String?) : ConfigValue
private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue> private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue>

View File

@@ -34,7 +34,6 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference; import androidx.preference.TwoStatePreference;
import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.ArrayUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.network.ims.VolteQueryImsState; import com.android.settings.network.ims.VolteQueryImsState;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -56,8 +55,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
Preference mPreference; Preference mPreference;
private PhoneCallStateTelephonyCallback mTelephonyCallback; private PhoneCallStateTelephonyCallback mTelephonyCallback;
private boolean mShow5gLimitedDialog; private boolean mShow5gLimitedDialog;
boolean mIsNrEnabledFromCarrierConfig;
private boolean mHas5gCapability;
private Integer mCallState; private Integer mCallState;
private final List<On4gLteUpdateListener> m4gLteListeners; private final List<On4gLteUpdateListener> m4gLteListeners;
@@ -94,9 +91,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
mShow5gLimitedDialog = carrierConfig.getBoolean( mShow5gLimitedDialog = carrierConfig.getBoolean(
CarrierConfigManager.KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL); 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; return this;
} }
@@ -247,10 +241,6 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
} }
mTelephonyManager.registerTelephonyCallback( mTelephonyManager.registerTelephonyCallback(
mContext.getMainExecutor(), mTelephonyCallback); mContext.getMainExecutor(), mTelephonyCallback);
final long supportedRadioBitmask = mTelephonyManager.getSupportedRadioAccessFamily();
mHas5gCapability =
(supportedRadioBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0;
} }
public void unregister() { public void unregister() {
@@ -269,8 +259,7 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc
} }
private boolean isDialogNeeded() { private boolean isDialogNeeded() {
Log.d(TAG, "Has5gCapability:" + mHas5gCapability); return mShow5gLimitedDialog && new NrRepository(mContext).isNrAvailable(mSubId);
return mShow5gLimitedDialog && mHas5gCapability && mIsNrEnabledFromCarrierConfig;
} }
private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) { private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) {

View File

@@ -25,31 +25,28 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R 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.MobileNetworkSettingsSearchItem
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
import com.android.settings.spa.preference.ComposePreferenceController import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /** Preference controller for "Voice over NR". */
* Preference controller for "Voice over NR". class NrAdvancedCallingPreferenceController
*/ @JvmOverloads
class NrAdvancedCallingPreferenceController @JvmOverloads constructor( constructor(
context: Context, context: Context,
key: String, key: String,
private val callStateRepository : CallStateRepository = CallStateRepository(context), private val voNrRepository: VoNrRepository = VoNrRepository(context),
private val callStateRepository: CallStateRepository = CallStateRepository(context),
) : ComposePreferenceController(context, key) { ) : ComposePreferenceController(context, key) {
private var subId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID private var subId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
private var repository: VoNrRepository? = null
private val searchItem = NrAdvancedCallingSearchItem(context) private val searchItem = NrAdvancedCallingSearchItem(context)
/** Initial this PreferenceController. */ /** Initial this PreferenceController. */
@JvmOverloads fun init(subId: Int) {
fun init(subId: Int, repository: VoNrRepository = VoNrRepository(mContext, subId)) {
this.subId = subId this.subId = subId
this.repository = repository
} }
override fun getAvailabilityStatus() = override fun getAvailabilityStatus() =
@@ -58,30 +55,32 @@ class NrAdvancedCallingPreferenceController @JvmOverloads constructor(
@Composable @Composable
override fun Content() { override fun Content() {
val summary = stringResource(R.string.nr_advanced_calling_summary) val summary = stringResource(R.string.nr_advanced_calling_summary)
val isInCall by remember { callStateRepository.isInCallFlow() } val isInCall by
.collectAsStateWithLifecycle(initialValue = false) remember { callStateRepository.isInCallFlow() }
val isEnabled by remember { .collectAsStateWithLifecycle(initialValue = false)
repository?.isVoNrEnabledFlow() ?: flowOf(false) val isVoNrEnabled by
}.collectAsStateWithLifecycle(initialValue = false) remember { voNrRepository.isVoNrEnabledFlow(subId) }
.collectAsStateWithLifecycle(initialValue = false)
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
SwitchPreference(object : SwitchPreferenceModel { SwitchPreference(
override val title = stringResource(R.string.nr_advanced_calling_title) object : SwitchPreferenceModel {
override val summary = { summary } override val title = stringResource(R.string.nr_advanced_calling_title)
override val changeable = { !isInCall } override val summary = { summary }
override val checked = { isEnabled } override val changeable = { !isInCall }
override val onCheckedChange: (Boolean) -> Unit = { newChecked -> override val checked = { isVoNrEnabled }
coroutineScope.launch { override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
repository?.setVoNrEnabled(newChecked) coroutineScope.launch { voNrRepository.setVoNrEnabled(subId, newChecked) }
} }
} }
}) )
} }
companion object { companion object {
class NrAdvancedCallingSearchItem(private val context: Context) : class NrAdvancedCallingSearchItem(private val context: Context) :
MobileNetworkSettingsSearchItem { 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? { override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? {
if (!isAvailable(subId)) return null if (!isAvailable(subId)) return null

View File

@@ -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"
}
}

View File

@@ -19,7 +19,6 @@ package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.util.Log import android.util.Log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -29,43 +28,43 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class VoNrRepository(private val context: Context, private val subId: Int) { class VoNrRepository(
private val telephonyManager = context.telephonyManager(subId) private val context: Context,
private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!! private val nrRepository: NrRepository = NrRepository(context),
) {
private val carrierConfigRepository = CarrierConfigRepository(context)
fun isVoNrAvailable(): Boolean { fun isVoNrAvailable(subId: Int): Boolean {
if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability()) return false if (!nrRepository.isNrAvailable(subId)) return false
val carrierConfig = carrierConfigManager.safeGetConfig( data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean)
keys = listOf( val carrierConfig =
CarrierConfigManager.KEY_VONR_ENABLED_BOOL, carrierConfigRepository.transformConfig(subId) {
CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, Config(
CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL),
), isVoNrSettingVisibility =
subId = subId, getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
) )
return carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL) && }
carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL) && return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
(carrierConfig.getIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)
?.isNotEmpty() ?: false)
} }
private fun has5gCapability() = fun isVoNrEnabledFlow(subId: Int): Flow<Boolean> {
((telephonyManager.supportedRadioAccessFamily and val telephonyManager = context.telephonyManager(subId)
TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0) return context
.also { Log.d(TAG, "[$subId] has5gCapability: $it") } .subscriptionsChangedFlow()
.map { telephonyManager.isVoNrEnabled }
fun isVoNrEnabledFlow(): Flow<Boolean> = context.subscriptionsChangedFlow() .conflate()
.map { telephonyManager.isVoNrEnabled } .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
.conflate() .flowOn(Dispatchers.Default)
.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")
} }
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 companion object {
private const val TAG = "VoNrRepository" private const val TAG = "VoNrRepository"
} }

View File

@@ -77,6 +77,19 @@ class CarrierConfigRepositoryTest {
assertThat(value).isEqualTo(99) 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 @Test
fun getString_returnValue() { fun getString_returnValue() {
val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING
@@ -104,7 +117,8 @@ class CarrierConfigRepositoryTest {
assertThat(carrierName) assertThat(carrierName)
.isEqualTo( .isEqualTo(
CarrierConfigManager.getDefaultConfig() CarrierConfigManager.getDefaultConfig()
.getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)) .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)
)
} }
@Test @Test

View File

@@ -41,91 +41,77 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class NrAdvancedCallingPreferenceControllerTest { class NrAdvancedCallingPreferenceControllerTest {
@get:Rule @get:Rule val composeTestRule = createComposeRule()
val composeTestRule = createComposeRule()
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {} private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
private val callStateRepository = mock<CallStateRepository> { private val callStateRepository =
on { isInCallFlow() } doReturn flowOf(false) mock<CallStateRepository> { on { isInCallFlow() } doReturn flowOf(false) }
}
private val voNrRepository = mock<VoNrRepository>() private val voNrRepository =
mock<VoNrRepository> { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) }
private val controller = NrAdvancedCallingPreferenceController( private val controller =
context = context, NrAdvancedCallingPreferenceController(
key = TEST_KEY, context = context,
callStateRepository = callStateRepository, key = TEST_KEY,
).apply { init(SUB_ID, voNrRepository) } voNrRepository = voNrRepository,
callStateRepository = callStateRepository,
)
.apply { init(SUB_ID) }
@Test @Test
fun isChecked_voNrEnabled_on() { fun isChecked_voNrEnabled_on() {
voNrRepository.stub { voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) }
on { isVoNrEnabledFlow() } doReturn flowOf(true)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsOn() .assertIsOn()
} }
@Test @Test
fun isChecked_voNrDisabled_off() { fun isChecked_voNrDisabled_off() {
voNrRepository.stub { voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) }
on { isVoNrEnabledFlow() } doReturn flowOf(false)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsOff() .assertIsOff()
} }
@Test @Test
fun isEnabled_notInCall_enabled() { fun isChangeable_notInCall_changeable() {
callStateRepository.stub { callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(false) }
on { isInCallFlow() } doReturn flowOf(false)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsEnabled() .assertIsEnabled()
} }
@Test @Test
fun isEnabled_inCall_notEnabled() { fun isChangeable_inCall_notChangeable() {
callStateRepository.stub { callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(true) }
on { isInCallFlow() } doReturn flowOf(true)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title)) composeTestRule
.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
.assertIsNotEnabled() .assertIsNotEnabled()
} }
@Test @Test
fun onClick_setVoNrEnabled(): Unit = runBlocking { fun onClick_setVoNrEnabled(): Unit = runBlocking {
voNrRepository.stub { voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) }
on { isVoNrEnabledFlow() } doReturn flowOf(false)
}
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onRoot().performClick() composeTestRule.onRoot().performClick()
verify(voNrRepository).setVoNrEnabled(true) verify(voNrRepository).setVoNrEnabled(SUB_ID, true)
} }
private companion object { private companion object {

View File

@@ -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<TelephonyManager> {
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
}
}

View File

@@ -19,17 +19,15 @@ package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.core.os.persistableBundleOf
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.anyVararg
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.spy import org.mockito.kotlin.spy
import org.mockito.kotlin.stub import org.mockito.kotlin.stub
@@ -38,127 +36,107 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class VoNrRepositoryTest { class VoNrRepositoryTest {
private val mockTelephonyManager = mock<TelephonyManager> { private val mockTelephonyManager =
on { createForSubscriptionId(SUB_ID) } doReturn mock mock<TelephonyManager> { 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 mockNrRepository = mock<NrRepository> { 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<CarrierConfigManager> {
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 @Test
fun isVoNrAvailable_visibleDisable_returnFalse() { fun isVoNrAvailable_visibleDisable_returnFalse() {
carrierConfig.apply { CarrierConfigRepository.setBooleanForTest(
putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, false) 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() assertThat(available).isFalse()
} }
@Test @Test
fun isVoNrAvailable_voNrDisabled_returnFalse() { fun isVoNrAvailable_voNrDisabled_returnFalse() {
carrierConfig.apply { CarrierConfigRepository.setBooleanForTest(
putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, false) subId = SUB_ID,
} key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
value = false,
)
val available = repository.isVoNrAvailable() val available = repository.isVoNrAvailable(SUB_ID)
assertThat(available).isFalse() assertThat(available).isFalse()
} }
@Test @Test
fun isVoNrAvailable_allEnabled_returnTrue() { fun isVoNrAvailable_allEnabled_returnTrue() {
mockTelephonyManager.stub { CarrierConfigRepository.setBooleanForTest(
on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR subId = SUB_ID,
} key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
carrierConfig.apply { value = true,
putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, true) )
putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, true) CarrierConfigRepository.setBooleanForTest(
putIntArray( subId = SUB_ID,
CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL,
intArrayOf(1, 2), value = true,
) )
}
val available = repository.isVoNrAvailable() val available = repository.isVoNrAvailable(SUB_ID)
assertThat(available).isTrue() assertThat(available).isTrue()
} }
@Test @Test
fun isVoNrAvailable_deviceNoNr_returnFalse() { fun isVoNrAvailable_noNr_returnFalse() {
mockTelephonyManager.stub { mockNrRepository.stub { on { isNrAvailable(SUB_ID) } doReturn false }
on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE
}
val available = repository.isVoNrAvailable() val available = repository.isVoNrAvailable(SUB_ID)
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()
assertThat(available).isFalse() assertThat(available).isFalse()
} }
@Test @Test
fun isVoNrEnabledFlow_voNrDisabled() = runBlocking { fun isVoNrEnabledFlow_voNrDisabled() = runBlocking {
mockTelephonyManager.stub { mockTelephonyManager.stub { on { isVoNrEnabled } doReturn false }
on { isVoNrEnabled } doReturn false
}
val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVoNrEnabled).isFalse() assertThat(isVoNrEnabled).isFalse()
} }
@Test @Test
fun isVoNrEnabledFlow_voNrEnabled() = runBlocking { fun isVoNrEnabledFlow_voNrEnabled() = runBlocking {
mockTelephonyManager.stub { mockTelephonyManager.stub { on { isVoNrEnabled } doReturn true }
on { isVoNrEnabled } doReturn true
}
val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull() val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVoNrEnabled).isTrue() assertThat(isVoNrEnabled).isTrue()
} }
@Test @Test
fun setVoNrEnabled(): Unit = runBlocking { fun setVoNrEnabled(): Unit = runBlocking {
repository.setVoNrEnabled(true) repository.setVoNrEnabled(SUB_ID, true)
verify(mockTelephonyManager).setVoNrEnabled(true) verify(mockTelephonyManager).setVoNrEnabled(true)
} }