Merge "Add the mobile data into new SIMs page" into main

This commit is contained in:
SongFerng Wang
2024-04-16 08:54:26 +00:00
committed by Android (Google) Code Review
7 changed files with 237 additions and 44 deletions

View File

@@ -46,15 +46,18 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.lifecycle.LifecycleRegistry
import com.android.settings.R import com.android.settings.R
import com.android.settings.SidecarFragment import com.android.settings.SidecarFragment
import com.android.settings.network.telephony.SubscriptionActionDialogActivity import com.android.settings.network.telephony.SubscriptionActionDialogActivity
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute
import com.android.settings.wifi.WifiPickerTrackerHelper
import com.android.settingslib.spa.SpaBaseDialogActivity import com.android.settingslib.spa.SpaBaseDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
@@ -74,6 +77,8 @@ import kotlinx.coroutines.launch
class SimOnboardingActivity : SpaBaseDialogActivity() { class SimOnboardingActivity : SpaBaseDialogActivity() {
lateinit var scope: CoroutineScope lateinit var scope: CoroutineScope
lateinit var wifiPickerTrackerHelper: WifiPickerTrackerHelper
lateinit var context: Context
lateinit var showStartingDialog: MutableState<Boolean> lateinit var showStartingDialog: MutableState<Boolean>
lateinit var showError: MutableState<ErrorType> lateinit var showError: MutableState<ErrorType>
lateinit var showProgressDialog: MutableState<Boolean> lateinit var showProgressDialog: MutableState<Boolean>
@@ -86,6 +91,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!this.userManager.isAdminUser) { if (!this.userManager.isAdminUser) {
Log.e(TAG, "It is not the admin user. Unable to toggle subscription.") Log.e(TAG, "It is not the admin user. Unable to toggle subscription.")
finish() finish()
@@ -152,7 +158,10 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> { CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> {
scope.launch { scope.launch {
onboardingService.startSetupPrimarySim(this@SimOnboardingActivity) onboardingService.startSetupPrimarySim(
this@SimOnboardingActivity,
wifiPickerTrackerHelper
)
} }
} }
@@ -184,6 +193,12 @@ class SimOnboardingActivity : SpaBaseDialogActivity() {
showDsdsProgressDialog = rememberSaveable { mutableStateOf(false) } showDsdsProgressDialog = rememberSaveable { mutableStateOf(false) }
showRestartDialog = rememberSaveable { mutableStateOf(false) } showRestartDialog = rememberSaveable { mutableStateOf(false) }
scope = rememberCoroutineScope() scope = rememberCoroutineScope()
context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
wifiPickerTrackerHelper = WifiPickerTrackerHelper(
LifecycleRegistry(lifecycleOwner), context,
null /* WifiPickerTrackerCallback */
)
registerSidecarReceiverFlow() registerSidecarReceiverFlow()

View File

@@ -30,6 +30,7 @@ import com.android.settings.spa.network.setAutomaticData
import com.android.settings.spa.network.setDefaultData import com.android.settings.spa.network.setDefaultData
import com.android.settings.spa.network.setDefaultSms import com.android.settings.spa.network.setDefaultSms
import com.android.settings.spa.network.setDefaultVoice import com.android.settings.spa.network.setDefaultVoice
import com.android.settings.wifi.WifiPickerTrackerHelper
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.flow.MutableStateFlow
@@ -336,14 +337,17 @@ class SimOnboardingService {
} }
} }
suspend fun startSetupPrimarySim(context: Context) { suspend fun startSetupPrimarySim(
context: Context,
wifiPickerTrackerHelper: WifiPickerTrackerHelper
) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
setDefaultVoice(subscriptionManager, targetPrimarySimCalls) setDefaultVoice(subscriptionManager, targetPrimarySimCalls)
setDefaultSms(subscriptionManager, targetPrimarySimTexts) setDefaultSms(subscriptionManager, targetPrimarySimTexts)
setDefaultData( setDefaultData(
context, context,
subscriptionManager, subscriptionManager,
null, wifiPickerTrackerHelper,
targetPrimarySimMobileData targetPrimarySimMobileData
) )
TelephonyRepository(context).setAutomaticData( TelephonyRepository(context).setAutomaticData(

View File

@@ -21,6 +21,8 @@ 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 android.util.Log
import com.android.settings.network.mobileDataEnabledFlow
import com.android.settings.wifi.WifiPickerTrackerHelper
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
@@ -62,6 +64,42 @@ class TelephonyRepository(
telephonyManager.setMobileDataPolicyEnabled(policy, enabled) telephonyManager.setMobileDataPolicyEnabled(policy, enabled)
} }
fun isDataEnabled(
subId: Int,
): Flow<Boolean> {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
Log.d(TAG, "register mobileDataEnabledFlow: [$subId]")
return context.mobileDataEnabledFlow(subId)
.map {
Log.d(TAG, "mobileDataEnabledFlow: receive mobile data [$subId] start")
val telephonyManager = context.telephonyManager(subId)
telephonyManager.isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER)
.also { Log.d(TAG, "mobileDataEnabledFlow: [$subId] isDataEnabled(): $it") }
}
}
fun setMobileData(
subId: Int,
enabled: Boolean,
wifiPickerTrackerHelper: WifiPickerTrackerHelper? = null
) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return
Log.d(TAG, "setMobileData: $enabled")
MobileNetworkUtils.setMobileDataEnabled(
context,
subId,
enabled /* enabled */,
true /* disableOtherSubscriptions */
)
if (wifiPickerTrackerHelper != null
&& !wifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(subId)
) {
wifiPickerTrackerHelper.setCarrierNetworkEnabled(enabled)
}
}
private companion object { private companion object {
private const val TAG = "TelephonyRepository" private const val TAG = "TelephonyRepository"
} }

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.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 MobileDataSwitchingPreference(
isMobileDataEnabled: () -> Boolean?,
setMobileDataEnabled: (newEnabled: Boolean) -> Unit,
) {
val mobileDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
val coroutineScope = rememberCoroutineScope()
SwitchPreference(
object : SwitchPreferenceModel {
override val title = stringResource(id = R.string.mobile_data_settings_title)
override val summary = { mobileDataSummary }
override val checked = { isMobileDataEnabled() }
override val onCheckedChange: (Boolean) -> Unit = { newEnabled ->
coroutineScope.launch(Dispatchers.Default) {
setMobileDataEnabled(newEnabled)
}
}
override val changeable:() -> Boolean = {true}
}
)
}

View File

@@ -38,11 +38,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel 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.TelephonyRepository 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
@@ -167,7 +168,7 @@ fun PageImpl(
defaultVoiceSubId: MutableIntState, defaultVoiceSubId: MutableIntState,
defaultSmsSubId: MutableIntState, defaultSmsSubId: MutableIntState,
defaultDataSubId: MutableIntState, defaultDataSubId: MutableIntState,
nonDds: MutableIntState nonDds: MutableIntState,
) { ) {
val selectableSubscriptionInfoList by selectableSubscriptionInfoListFlow val selectableSubscriptionInfoList by selectableSubscriptionInfoListFlow
.collectAsStateWithLifecycle(initialValue = emptyList()) .collectAsStateWithLifecycle(initialValue = emptyList())
@@ -175,22 +176,76 @@ fun PageImpl(
val stringSims = stringResource(R.string.provider_network_settings_title) val stringSims = stringResource(R.string.provider_network_settings_title)
RegularScaffold(title = stringSims) { RegularScaffold(title = stringSims) {
SimsSection(selectableSubscriptionInfoList) SimsSection(selectableSubscriptionInfoList)
MobileDataSectionImpl(defaultDataSubId,
nonDds,
)
PrimarySimSectionImpl( PrimarySimSectionImpl(
selectableSubscriptionInfoListFlow, selectableSubscriptionInfoListFlow,
defaultVoiceSubId, defaultVoiceSubId,
defaultSmsSubId, defaultSmsSubId,
defaultDataSubId, defaultDataSubId,
nonDds
) )
} }
} }
@Composable
fun MobileDataSectionImpl(
mobileDataSelectedId: MutableIntState,
nonDds: MutableIntState,
) {
val context = LocalContext.current
val localLifecycleOwner = LocalLifecycleOwner.current
val wifiPickerTrackerHelper = getWifiPickerTrackerHelper(context, localLifecycleOwner)
val subscriptionManager: SubscriptionManager? =
context.getSystemService(SubscriptionManager::class.java)
Category(title = stringResource(id = R.string.mobile_data_settings_title)) {
val isAutoDataEnabled by remember(nonDds.intValue) {
TelephonyRepository(context).isMobileDataPolicyEnabledFlow(
subId = nonDds.intValue,
policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH
)
}.collectAsStateWithLifecycle(initialValue = null)
val mobileDataStateChanged by remember(mobileDataSelectedId.intValue) {
TelephonyRepository(context).isDataEnabled(mobileDataSelectedId.intValue)
}.collectAsStateWithLifecycle(initialValue = false)
val coroutineScope = rememberCoroutineScope()
MobileDataSwitchingPreference(
isMobileDataEnabled = { mobileDataStateChanged },
setMobileDataEnabled = { newEnabled ->
coroutineScope.launch {
setMobileData(
context,
subscriptionManager,
wifiPickerTrackerHelper,
mobileDataSelectedId.intValue,
newEnabled
)
}
},
)
if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
AutomaticDataSwitchingPreference(
isAutoDataEnabled = { isAutoDataEnabled },
setAutoDataEnabled = { newEnabled ->
TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled)
},
)
}
}
}
@Composable @Composable
fun PrimarySimImpl( fun PrimarySimImpl(
primarySimInfo: PrimarySimInfo, primarySimInfo: PrimarySimInfo,
callsSelectedId: MutableIntState, callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState, textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState,
wifiPickerTrackerHelper: WifiPickerTrackerHelper? = null,
subscriptionManager: SubscriptionManager? = subscriptionManager: SubscriptionManager? =
LocalContext.current.getSystemService(SubscriptionManager::class.java), LocalContext.current.getSystemService(SubscriptionManager::class.java),
coroutineScope: CoroutineScope = rememberCoroutineScope(), coroutineScope: CoroutineScope = rememberCoroutineScope(),
@@ -208,20 +263,15 @@ fun PrimarySimImpl(
} }
}, },
actionSetMobileData: (Int) -> Unit = { actionSetMobileData: (Int) -> Unit = {
mobileDataSelectedId.intValue = it
coroutineScope.launch { coroutineScope.launch {
// TODO: to fix the WifiPickerTracker crash when create
// the wifiPickerTrackerHelper
setDefaultData( setDefaultData(
context, context,
subscriptionManager, subscriptionManager,
null/*wifiPickerTrackerHelper*/, wifiPickerTrackerHelper,
it it
) )
} }
}, },
isAutoDataEnabled: () -> Boolean?,
setAutoDataEnabled: (newEnabled: Boolean) -> Unit,
) { ) {
CreatePrimarySimListPreference( CreatePrimarySimListPreference(
stringResource(id = R.string.primary_sim_calls_title), stringResource(id = R.string.primary_sim_calls_title),
@@ -244,8 +294,6 @@ fun PrimarySimImpl(
Icons.Outlined.DataUsage, Icons.Outlined.DataUsage,
actionSetMobileData actionSetMobileData
) )
AutomaticDataSwitchingPreference(isAutoDataEnabled, setAutoDataEnabled)
} }
@Composable @Composable
@@ -254,9 +302,11 @@ fun PrimarySimSectionImpl(
callsSelectedId: MutableIntState, callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState, textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState,
nonDds: MutableIntState,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val localLifecycleOwner = LocalLifecycleOwner.current
val wifiPickerTrackerHelper = getWifiPickerTrackerHelper(context, localLifecycleOwner)
val primarySimInfo = remember(subscriptionInfoListFlow) { val primarySimInfo = remember(subscriptionInfoListFlow) {
subscriptionInfoListFlow subscriptionInfoListFlow
.map { subscriptionInfoList -> .map { subscriptionInfoList ->
@@ -267,25 +317,25 @@ 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,
isAutoDataEnabled = { isAutoDataEnabled }, wifiPickerTrackerHelper
setAutoDataEnabled = { newEnabled ->
TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled)
},
) )
} }
} }
private fun getWifiPickerTrackerHelper(
context: Context,
lifecycleOwner: LifecycleOwner
): WifiPickerTrackerHelper {
return WifiPickerTrackerHelper(
LifecycleRegistry(lifecycleOwner), context,
null /* WifiPickerTrackerCallback */
)
}
private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> = private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> =
merge( merge(
flowOf(null), // kick an initial value flowOf(null), // kick an initial value
@@ -334,19 +384,28 @@ suspend fun setDefaultData(
subscriptionManager: SubscriptionManager?, subscriptionManager: SubscriptionManager?,
wifiPickerTrackerHelper: WifiPickerTrackerHelper?, wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
subId: Int subId: Int
): Unit =
setMobileData(
context,
subscriptionManager,
wifiPickerTrackerHelper,
subId,
true
)
suspend fun setMobileData(
context: Context,
subscriptionManager: SubscriptionManager?,
wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
subId: Int,
enabled: Boolean,
): Unit = ): Unit =
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
subscriptionManager?.setDefaultDataSubId(subId) Log.d(NetworkCellularGroupProvider.name, "setMobileData: $enabled")
Log.d(NetworkCellularGroupProvider.name, "setMobileDataEnabled: true") if (enabled) {
MobileNetworkUtils.setMobileDataEnabled( Log.d(NetworkCellularGroupProvider.name, "setDefaultData: [$subId]")
context, subscriptionManager?.setDefaultDataSubId(subId)
subId,
true /* enabled */,
true /* disableOtherSubscriptions */
)
if (wifiPickerTrackerHelper != null
&& !wifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(subId)
) {
wifiPickerTrackerHelper.setCarrierNetworkEnabled(true)
} }
} TelephonyRepository(context)
.setMobileData(subId, enabled, wifiPickerTrackerHelper)
}

View File

@@ -102,18 +102,21 @@ fun SimOnboardingPrimarySimImpl(
mobileDataSelectedId = mobileDataSelectedId, mobileDataSelectedId = mobileDataSelectedId,
actionSetCalls = { actionSetCalls = {
callsSelectedId.intValue = it callsSelectedId.intValue = it
onboardingService.targetPrimarySimCalls = it}, onboardingService.targetPrimarySimCalls = it
},
actionSetTexts = { actionSetTexts = {
textsSelectedId.intValue = it textsSelectedId.intValue = it
onboardingService.targetPrimarySimTexts = it}, onboardingService.targetPrimarySimTexts = it
},
actionSetMobileData = { actionSetMobileData = {
mobileDataSelectedId.intValue = it mobileDataSelectedId.intValue = it
onboardingService.targetPrimarySimMobileData = it}, onboardingService.targetPrimarySimMobileData = it
isAutoDataEnabled = { isAutoDataEnabled }, }
)
AutomaticDataSwitchingPreference(isAutoDataEnabled = { isAutoDataEnabled },
setAutoDataEnabled = { newEnabled -> setAutoDataEnabled = { newEnabled ->
onboardingService.targetPrimarySimAutoDataSwitch.value = newEnabled onboardingService.targetPrimarySimAutoDataSwitch.value = newEnabled
}, })
)
} }
} }

View File

@@ -91,6 +91,30 @@ class TelephonyRepositoryTest {
.setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true) .setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true)
} }
@Test
fun isDataEnabled_invalidSub_returnFalse() = runBlocking {
val state = repository.isDataEnabled(
subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
)
assertThat(state.firstWithTimeoutOrNull()).isFalse()
}
@Test
fun isDataEnabled_validSub_returnPolicyState() = runBlocking {
mockTelephonyManager.stub {
on {
isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER)
} doReturn true
}
val state = repository.isDataEnabled(
subId = SUB_ID,
)
assertThat(state.firstWithTimeoutOrNull()).isTrue()
}
@Test @Test
fun telephonyCallbackFlow_callbackRegistered() = runBlocking { fun telephonyCallbackFlow_callbackRegistered() = runBlocking {
val flow = context.telephonyCallbackFlow<Unit>(SUB_ID) { val flow = context.telephonyCallbackFlow<Unit>(SUB_ID) {