Refactor AutomaticDataSwitchingPreference

Split into AutomaticDataSwitchingPreference.kt, and use
isMobileDataPolicyEnabledFlow.

Bug: 329061940
Test: manual - Set Automatic data switching
Test: unit test
Change-Id: I878ed70328307c0a5dba6dfb461ff5a85efbcf88
This commit is contained in:
Chaohui Wang
2024-03-18 17:47:51 +08:00
parent 3a12c7b18b
commit bfd8a517be
6 changed files with 173 additions and 87 deletions

View File

@@ -24,6 +24,7 @@ import android.telephony.UiccCardInfo
import android.telephony.UiccSlotInfo import android.telephony.UiccSlotInfo
import android.util.Log import android.util.Log
import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.network.telephony.TelephonyRepository
import com.android.settings.sim.SimActivationNotifier import com.android.settings.sim.SimActivationNotifier
import com.android.settings.spa.network.setAutomaticData import com.android.settings.spa.network.setAutomaticData
import com.android.settings.spa.network.setDefaultData import com.android.settings.spa.network.setDefaultData
@@ -31,6 +32,7 @@ import com.android.settings.spa.network.setDefaultSms
import com.android.settings.spa.network.setDefaultVoice import com.android.settings.spa.network.setDefaultVoice
import com.android.settingslib.utils.ThreadUtils import com.android.settingslib.utils.ThreadUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class SimOnboardingService { class SimOnboardingService {
@@ -46,7 +48,7 @@ class SimOnboardingService {
var targetPrimarySimCalls: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var targetPrimarySimCalls: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
var targetPrimarySimTexts: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var targetPrimarySimTexts: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
var targetPrimarySimMobileData: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var targetPrimarySimMobileData: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
var targetPrimarySimAutoDataSwitch: Boolean = false val targetPrimarySimAutoDataSwitch = MutableStateFlow(false)
var targetNonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var targetNonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
get() { get() {
if(targetPrimarySimMobileData == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { if(targetPrimarySimMobileData == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
@@ -349,19 +351,10 @@ class SimOnboardingService {
null, null,
targetPrimarySimMobileData targetPrimarySimMobileData
) )
TelephonyRepository(context).setAutomaticData(
var nonDds = targetNonDds targetNonDds,
Log.d( targetPrimarySimAutoDataSwitch.value
TAG,
"setAutomaticData: targetNonDds: $nonDds," +
" targetPrimarySimAutoDataSwitch: $targetPrimarySimAutoDataSwitch"
) )
if (nonDds != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(nonDds)
setAutomaticData(telephonyManagerForNonDds, targetPrimarySimAutoDataSwitch)
}
} }
// no next action, send finish // no next action, send finish
callback(CallbackType.CALLBACK_FINISH) callback(CallbackType.CALLBACK_FINISH)

View File

@@ -17,8 +17,10 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.util.Log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.ProducerScope
@@ -26,15 +28,51 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
class TelephonyRepository(
private val context: Context,
private val subscriptionsChangedFlow: Flow<Unit> = context.subscriptionsChangedFlow(),
) {
fun isMobileDataPolicyEnabledFlow(
subId: Int,
@TelephonyManager.MobileDataPolicy policy: Int,
): Flow<Boolean> {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
val telephonyManager = context.telephonyManager(subId)
return subscriptionsChangedFlow.map {
telephonyManager.isMobileDataPolicyEnabled(policy)
.also { Log.d(TAG, "[$subId] isMobileDataPolicyEnabled($policy): $it") }
}.conflate().flowOn(Dispatchers.Default)
}
fun setMobileDataPolicyEnabled(
subId: Int,
@TelephonyManager.MobileDataPolicy policy: Int,
enabled: Boolean,
) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return
val telephonyManager = context.telephonyManager(subId)
Log.d(TAG, "[$subId] setMobileDataPolicyEnabled($policy): $enabled")
telephonyManager.setMobileDataPolicyEnabled(policy, enabled)
}
private companion object {
private const val TAG = "TelephonyRepository"
}
}
/** Creates an instance of a cold Flow for Telephony callback of given [subId]. */ /** Creates an instance of a cold Flow for Telephony callback of given [subId]. */
fun <T> Context.telephonyCallbackFlow( fun <T> Context.telephonyCallbackFlow(
subId: Int, subId: Int,
block: ProducerScope<T>.() -> TelephonyCallback, block: ProducerScope<T>.() -> TelephonyCallback,
): Flow<T> = callbackFlow { ): Flow<T> = callbackFlow {
val telephonyManager = getSystemService(TelephonyManager::class.java)!! val telephonyManager = telephonyManager(subId)
.createForSubscriptionId(subId)
val callback = block() val callback = block()
@@ -42,3 +80,7 @@ fun <T> Context.telephonyCallbackFlow(
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}.conflate().flowOn(Dispatchers.Default) }.conflate().flowOn(Dispatchers.Default)
fun Context.telephonyManager(subId: Int): TelephonyManager =
getSystemService(TelephonyManager::class.java)!!
.createForSubscriptionId(subId)

View File

@@ -0,0 +1,58 @@
/*
* 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.spa.network
import android.telephony.TelephonyManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.network.telephony.TelephonyRepository
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun AutomaticDataSwitchingPreference(
isAutoDataEnabled: () -> Boolean?,
setAutoDataEnabled: (newEnabled: Boolean) -> Unit,
) {
val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
val coroutineScope = rememberCoroutineScope()
SwitchPreference(
object : SwitchPreferenceModel {
override val title = stringResource(id = R.string.primary_sim_automatic_data_title)
override val summary = { autoDataSummary }
override val checked = { isAutoDataEnabled() }
override val onCheckedChange: (Boolean) -> Unit = { newEnabled ->
coroutineScope.launch(Dispatchers.Default) {
setAutoDataEnabled(newEnabled)
}
}
}
)
}
fun TelephonyRepository.setAutomaticData(subId: Int, newEnabled: Boolean) {
setMobileDataPolicyEnabled(
subId = subId,
policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
enabled = newEnabled,
)
//TODO: setup backup calling
}

View File

@@ -30,7 +30,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -44,6 +43,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.SubscriptionInfoListViewModel import com.android.settings.network.SubscriptionInfoListViewModel
import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.TelephonyRepository
import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo
import com.android.settings.wifi.WifiPickerTrackerHelper import com.android.settings.wifi.WifiPickerTrackerHelper
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -53,8 +53,6 @@ import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
@@ -193,7 +191,6 @@ fun PrimarySimImpl(
callsSelectedId: MutableIntState, callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState, textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState,
nonDds: MutableIntState,
subscriptionManager: SubscriptionManager? = subscriptionManager: SubscriptionManager? =
LocalContext.current.getSystemService(SubscriptionManager::class.java), LocalContext.current.getSystemService(SubscriptionManager::class.java),
coroutineScope: CoroutineScope = rememberCoroutineScope(), coroutineScope: CoroutineScope = rememberCoroutineScope(),
@@ -223,23 +220,9 @@ fun PrimarySimImpl(
) )
} }
}, },
actionSetAutoDataSwitch: (Boolean) -> Unit = { newState -> isAutoDataEnabled: () -> Boolean?,
coroutineScope.launch { setAutoDataEnabled: (newEnabled: Boolean) -> Unit,
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(nonDds.intValue)
Log.d(NetworkCellularGroupProvider.name, "NonDds:${nonDds.intValue} setAutomaticData")
setAutomaticData(telephonyManagerForNonDds, newState)
}
},
) { ) {
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(nonDds.intValue)
val automaticDataChecked = rememberSaveable() {
mutableStateOf(false)
}
CreatePrimarySimListPreference( CreatePrimarySimListPreference(
stringResource(id = R.string.primary_sim_calls_title), stringResource(id = R.string.primary_sim_calls_title),
primarySimInfo.callsAndSmsList, primarySimInfo.callsAndSmsList,
@@ -262,31 +245,7 @@ fun PrimarySimImpl(
actionSetMobileData actionSetMobileData
) )
val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title) AutomaticDataSwitchingPreference(isAutoDataEnabled, setAutoDataEnabled)
val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
SwitchPreference(
object : SwitchPreferenceModel {
override val title = autoDataTitle
override val summary = { autoDataSummary }
override val checked = {
if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
coroutineScope.launch {
automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds)
Log.d(
NetworkCellularGroupProvider.name,
"NonDds:${nonDds.intValue}" +
"getAutomaticData:${automaticDataChecked.value}"
)
}
}
automaticDataChecked.value
}
override val onCheckedChange: ((Boolean) -> Unit)? = {
automaticDataChecked.value = it
actionSetAutoDataSwitch(it)
}
}
)
} }
@Composable @Composable
@@ -308,12 +267,21 @@ fun PrimarySimSectionImpl(
}.collectAsStateWithLifecycle(initialValue = null).value ?: return }.collectAsStateWithLifecycle(initialValue = null).value ?: return
Category(title = stringResource(id = R.string.primary_sim_title)) { Category(title = stringResource(id = R.string.primary_sim_title)) {
val isAutoDataEnabled by remember(nonDds.intValue) {
TelephonyRepository(context).isMobileDataPolicyEnabledFlow(
subId = nonDds.intValue,
policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH
)
}.collectAsStateWithLifecycle(initialValue = null)
PrimarySimImpl( PrimarySimImpl(
primarySimInfo, primarySimInfo,
callsSelectedId, callsSelectedId,
textsSelectedId, textsSelectedId,
mobileDataSelectedId, mobileDataSelectedId,
nonDds isAutoDataEnabled = { isAutoDataEnabled },
setAutoDataEnabled = { newEnabled ->
TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled)
},
) )
} }
} }
@@ -381,23 +349,3 @@ suspend fun setDefaultData(
wifiPickerTrackerHelper.setCarrierNetworkEnabled(true) wifiPickerTrackerHelper.setCarrierNetworkEnabled(true)
} }
} }
suspend fun getAutomaticData(telephonyManagerForNonDds: TelephonyManager?): Boolean =
withContext(Dispatchers.Default) {
telephonyManagerForNonDds != null
&& telephonyManagerForNonDds.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
}
suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit =
withContext(Dispatchers.Default) {
Log.d(
NetworkCellularGroupProvider.name,
"setAutomaticData: MOBILE_DATA_POLICY_AUTO_DATA_SWITCH as $newState"
)
telephonyManager?.setMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
newState
)
//TODO: setup backup calling
}

View File

@@ -23,6 +23,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SignalCellularAlt import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -75,9 +76,6 @@ fun SimOnboardingPrimarySimImpl(
val mobileDataSelectedId = rememberSaveable { val mobileDataSelectedId = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
} }
val nonDdsRemember = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
Column(Modifier.padding(SettingsDimension.itemPadding)) { Column(Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg)) SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
@@ -94,12 +92,14 @@ fun SimOnboardingPrimarySimImpl(
callsSelectedId.intValue = onboardingService.targetPrimarySimCalls callsSelectedId.intValue = onboardingService.targetPrimarySimCalls
textsSelectedId.intValue = onboardingService.targetPrimarySimTexts textsSelectedId.intValue = onboardingService.targetPrimarySimTexts
mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData
val isAutoDataEnabled by
onboardingService.targetPrimarySimAutoDataSwitch
.collectAsStateWithLifecycle(initialValue = null)
PrimarySimImpl( PrimarySimImpl(
primarySimInfo = primarySimInfo, primarySimInfo = primarySimInfo,
callsSelectedId = callsSelectedId, callsSelectedId = callsSelectedId,
textsSelectedId = textsSelectedId, textsSelectedId = textsSelectedId,
mobileDataSelectedId = mobileDataSelectedId, mobileDataSelectedId = mobileDataSelectedId,
nonDds = nonDdsRemember,
actionSetCalls = { actionSetCalls = {
callsSelectedId.intValue = it callsSelectedId.intValue = it
onboardingService.targetPrimarySimCalls = it}, onboardingService.targetPrimarySimCalls = it},
@@ -109,8 +109,10 @@ fun SimOnboardingPrimarySimImpl(
actionSetMobileData = { actionSetMobileData = {
mobileDataSelectedId.intValue = it mobileDataSelectedId.intValue = it
onboardingService.targetPrimarySimMobileData = it}, onboardingService.targetPrimarySimMobileData = it},
actionSetAutoDataSwitch = { isAutoDataEnabled = { isAutoDataEnabled },
onboardingService.targetPrimarySimAutoDataSwitch = it}, setAutoDataEnabled = { newEnabled ->
onboardingService.targetPrimarySimAutoDataSwitch.value = newEnabled
},
) )
} }
} }

View File

@@ -17,12 +17,14 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
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.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -31,6 +33,7 @@ import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
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.verify import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@@ -48,6 +51,46 @@ class TelephonyRepositoryTest {
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
} }
private val repository = TelephonyRepository(context, flowOf(Unit))
@Test
fun isMobileDataPolicyEnabledFlow_invalidSub_returnFalse() = runBlocking {
val flow = repository.isMobileDataPolicyEnabledFlow(
subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
)
assertThat(flow.firstWithTimeoutOrNull()).isFalse()
}
@Test
fun isMobileDataPolicyEnabledFlow_validSub_returnPolicyState() = runBlocking {
mockTelephonyManager.stub {
on {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
val flow = repository.isMobileDataPolicyEnabledFlow(
subId = SUB_ID,
policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
)
assertThat(flow.firstWithTimeoutOrNull()).isTrue()
}
@Test
fun setMobileDataPolicyEnabled() = runBlocking {
repository.setMobileDataPolicyEnabled(
subId = SUB_ID,
policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
enabled = true
)
verify(mockTelephonyManager)
.setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true)
}
@Test @Test
fun telephonyCallbackFlow_callbackRegistered() = runBlocking { fun telephonyCallbackFlow_callbackRegistered() = runBlocking {
val flow = context.telephonyCallbackFlow<Unit>(SUB_ID) { val flow = context.telephonyCallbackFlow<Unit>(SUB_ID) {