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

View File

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

View File

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

View File

@@ -91,6 +91,30 @@ class TelephonyRepositoryTest {
.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
fun telephonyCallbackFlow_callbackRegistered() = runBlocking {
val flow = context.telephonyCallbackFlow<Unit>(SUB_ID) {