Loading res/xml/network_provider_internet.xml +0 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,6 @@ settings:keywords="@string/keywords_more_mobile_networks" settings:userRestriction="no_config_mobile_networks" settings:isPreferenceVisible="@bool/config_show_sim_info" settings:allowDividerAbove="true" settings:useAdminDisabledSummary="true" settings:searchable="@bool/config_show_sim_info"/> Loading src/com/android/settings/network/MobileNetworkListFragment.kt +12 −0 Original line number Diff line number Diff line Loading @@ -26,8 +26,11 @@ import androidx.preference.Preference import com.android.settings.R import com.android.settings.SettingsPreferenceFragment import com.android.settings.dashboard.DashboardFragment import com.android.settings.flags.Flags import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.search.BaseSearchIndexProvider import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.network.NetworkCellularGroupProvider import com.android.settingslib.search.SearchIndexable import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spaprivileged.framework.common.userManager Loading @@ -40,6 +43,15 @@ class MobileNetworkListFragment : DashboardFragment() { collectAirplaneModeAndFinishIfOn() } override fun onCreate(icicle: Bundle?) { super.onCreate(icicle) if (Flags.isDualSimOnboardingEnabled()) { context?.startSpaActivity(NetworkCellularGroupProvider.name); finish() } } override fun onResume() { super.onResume() // Disable the animation of the preference list Loading src/com/android/settings/network/SubscriptionInfoListViewModel.kt +12 −0 Original line number Diff line number Diff line Loading @@ -32,7 +32,19 @@ class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel application.getSystemService(SubscriptionManager::class.java)!! private val scope = viewModelScope + Dispatchers.Default /** * Getting the active Subscription list */ //ToDo: renaming the function name val subscriptionInfoListFlow = application.subscriptionsChangedFlow().map { SubscriptionUtil.getActiveSubscriptions(subscriptionManager) }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) /** * Getting the Selectable SubscriptionInfo List from the SubscriptionManager's * getAvailableSubscriptionInfoList */ val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map { SubscriptionUtil.getSelectableSubscriptionInfoList(application) }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) } src/com/android/settings/spa/SettingsSpaEnvironment.kt +2 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import com.android.settings.spa.development.UsageStatsPageProvider import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider import com.android.settings.spa.home.HomePageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider import com.android.settings.spa.network.NetworkCellularGroupProvider import com.android.settings.spa.network.SimOnboardingPageProvider import com.android.settings.spa.notification.AppListNotificationsPageProvider import com.android.settings.spa.notification.NotificationMainPageProvider Loading Loading @@ -118,6 +119,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { ApnEditPageProvider, SimOnboardingPageProvider, BatteryOptimizationModeAppListPageProvider, NetworkCellularGroupProvider, ) override val logger = if (FeatureFlagUtils.isEnabled( Loading src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt 0 → 100644 +465 −0 Original line number Diff line number Diff line /* * 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.app.Application import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Bundle import android.os.UserManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.euicc.EuiccManager import android.util.Log import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Message import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.DataUsage import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.graphics.vector.ImageVector 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 com.android.settings.R import com.android.settings.network.SubscriptionInfoListViewModel import com.android.settings.network.SubscriptionUtil import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.wifi.WifiPickerTrackerHelper import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spa.widget.preference.ListPreferenceOption import com.android.settingslib.spa.widget.preference.Preference 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.preference.TwoTargetSwitchPreference import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Category import com.android.settingslib.spa.widget.ui.SettingsIcon import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * Showing the sim onboarding which is the process flow of sim switching on. */ object NetworkCellularGroupProvider : SettingsPageProvider { override val name = "NetworkCellularGroupProvider" private lateinit var subscriptionViewModel: SubscriptionInfoListViewModel private val owner = createSettingsPage() var selectableSubscriptionInfoList: List<SubscriptionInfo> = listOf() var defaultVoiceSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var defaultSmsSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var defaultDataSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var nonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner) .setUiLayoutFn { // never using Preference(object : PreferenceModel { override val title = name override val onClick = navigator(name) }) } @Composable override fun Page(arguments: Bundle?) { val context = LocalContext.current var selectableSubscriptionInfoListRemember = remember { mutableListOf<SubscriptionInfo>().toMutableStateList() } var callsSelectedId = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } var textsSelectedId = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } var mobileDataSelectedId = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } var nonDdsRemember = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } subscriptionViewModel = SubscriptionInfoListViewModel( context.applicationContext as Application) allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow) .collectLatestWithLifecycle(LocalLifecycleOwner.current) { selectableSubscriptionInfoListRemember.clear() selectableSubscriptionInfoListRemember.addAll(selectableSubscriptionInfoList) callsSelectedId.intValue = defaultVoiceSubId textsSelectedId.intValue = defaultSmsSubId mobileDataSelectedId.intValue = defaultDataSubId nonDdsRemember.intValue = nonDds } PageImpl(selectableSubscriptionInfoListRemember, callsSelectedId, textsSelectedId, mobileDataSelectedId, nonDdsRemember) } private fun allOfFlows(context: Context, selectableSubscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) = combine( selectableSubscriptionInfoListFlow, context.defaultVoiceSubscriptionFlow(), context.defaultSmsSubscriptionFlow(), context.defaultDefaultDataSubscriptionFlow(), NetworkCellularGroupProvider::refreshUiStates, ).flowOn(Dispatchers.Default) fun refreshUiStates( inputSelectableSubscriptionInfoList: List<SubscriptionInfo>, inputDefaultVoiceSubId: Int, inputDefaultSmsSubId: Int, inputDefaultDateSubId: Int ): Unit { selectableSubscriptionInfoList = inputSelectableSubscriptionInfoList defaultVoiceSubId = inputDefaultVoiceSubId defaultSmsSubId = inputDefaultSmsSubId defaultDataSubId = inputDefaultDateSubId nonDds = if (defaultDataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { SubscriptionManager.INVALID_SUBSCRIPTION_ID } else { selectableSubscriptionInfoList .filter { info -> (info.simSlotIndex != -1) && (info.subscriptionId != defaultDataSubId) } .map { it.subscriptionId } .firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID } } } @Composable fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>, defaultVoiceSubId: MutableIntState, defaultSmsSubId: MutableIntState, defaultDataSubId: MutableIntState, nonDds: MutableIntState) { val context = LocalContext.current var activeSubscriptionInfoList: List<SubscriptionInfo> = selectableSubscriptionInfoList.filter { subscriptionInfo -> subscriptionInfo.simSlotIndex != -1 } var subscriptionManager = context.getSystemService(SubscriptionManager::class.java) val stringSims = stringResource(R.string.provider_network_settings_title) RegularScaffold(title = stringSims) { SimsSectionImpl( context, subscriptionManager, selectableSubscriptionInfoList ) PrimarySimSectionImpl( subscriptionManager, activeSubscriptionInfoList, defaultVoiceSubId, defaultSmsSubId, defaultDataSubId, nonDds ) } } @Composable fun SimsSectionImpl( context: Context, subscriptionManager: SubscriptionManager?, subscriptionInfoList: List<SubscriptionInfo> ) { val coroutineScope = rememberCoroutineScope() for (subInfo in subscriptionInfoList) { val checked = rememberSaveable() { mutableStateOf(false) } //TODO: Add the Restricted TwoTargetSwitchPreference in SPA TwoTargetSwitchPreference(remember { object : SwitchPreferenceModel { override val title = subInfo.displayName.toString() override val summary = { subInfo.number } override val checked = { coroutineScope.launch { withContext(Dispatchers.Default) { checked.value = subscriptionManager?.isSubscriptionEnabled( subInfo.subscriptionId)?:false } } checked.value } override val onCheckedChange = { newChecked: Boolean -> startToggleSubscriptionDialog(context, subInfo, newChecked) } } }) { startMobileNetworkSettings(context, subInfo) } } // + add sim if (showEuiccSettings(context)) { RestrictedPreference( model = object : PreferenceModel { override val title = stringResource(id = R.string.mobile_network_list_add_more) override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) } override val onClick = { startAddSimFlow(context) } }, restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)), ) } } @Composable fun PrimarySimSectionImpl( subscriptionManager: SubscriptionManager?, activeSubscriptionInfoList: List<SubscriptionInfo>, callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, nonDds: MutableIntState ) { var state = rememberSaveable { mutableStateOf(false) } var callsAndSmsList = remember { mutableListOf(ListPreferenceOption(id = -1, text = "Loading")) } var dataList = remember { mutableListOf(ListPreferenceOption(id = -1, text = "Loading")) } if (activeSubscriptionInfoList.size >= 2) { state.value = true callsAndSmsList.clear() dataList.clear() for (info in activeSubscriptionInfoList) { var item = ListPreferenceOption( id = info.subscriptionId, text = "${info.displayName}" ) callsAndSmsList.add(item) dataList.add(item) } callsAndSmsList.add(ListPreferenceOption( id = SubscriptionManager.INVALID_SUBSCRIPTION_ID, text = stringResource(id = R.string.sim_calls_ask_first_prefs_title) )) } else { // hide the primary sim state.value = false Log.d("NetworkCellularGroupProvider", "Hide primary sim") } if (state.value) { val coroutineScope = rememberCoroutineScope() var context = LocalContext.current val telephonyManagerForNonDds: TelephonyManager? = context.getSystemService(TelephonyManager::class.java) ?.createForSubscriptionId(nonDds.intValue) val automaticDataChecked = rememberSaveable() { mutableStateOf(false) } Category(title = stringResource(id = R.string.primary_sim_title)) { createPrimarySimListPreference( stringResource(id = R.string.primary_sim_calls_title), callsAndSmsList, callsSelectedId, ImageVector.vectorResource(R.drawable.ic_phone), ) { callsSelectedId.intValue = it coroutineScope.launch { setDefaultVoice(subscriptionManager, it) } } createPrimarySimListPreference( stringResource(id = R.string.primary_sim_texts_title), callsAndSmsList, textsSelectedId, Icons.AutoMirrored.Outlined.Message, ) { textsSelectedId.intValue = it coroutineScope.launch { setDefaultSms(subscriptionManager, it) } } createPrimarySimListPreference( stringResource(id = R.string.mobile_data_settings_title), dataList, mobileDataSelectedId, Icons.Outlined.DataUsage, ) { mobileDataSelectedId.intValue = it coroutineScope.launch { // TODO: to fix the WifiPickerTracker crash when create // the wifiPickerTrackerHelper setDefaultData(context, subscriptionManager, null/*wifiPickerTrackerHelper*/, it) } } } val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title) val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg) SwitchPreference(remember { object : SwitchPreferenceModel { override val title = autoDataTitle override val summary = { autoDataSummary } override val changeable: () -> Boolean = { nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID } override val checked = { coroutineScope.launch { withContext(Dispatchers.Default) { automaticDataChecked.value = telephonyManagerForNonDds != null && telephonyManagerForNonDds.isMobileDataPolicyEnabled( TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH) } } automaticDataChecked.value } override val onCheckedChange: ((Boolean) -> Unit)? = { newChecked: Boolean -> coroutineScope.launch { setAutomaticData(telephonyManagerForNonDds, newChecked) } } } }) } } private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> = merge( flowOf(null), // kick an initial value broadcastReceiverFlow( IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED) ), ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() } .conflate().flowOn(Dispatchers.Default) private fun Context.defaultSmsSubscriptionFlow(): Flow<Int> = merge( flowOf(null), // kick an initial value broadcastReceiverFlow( IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED) ), ).map { SubscriptionManager.getDefaultSmsSubscriptionId() } .conflate().flowOn(Dispatchers.Default) private fun Context.defaultDefaultDataSubscriptionFlow(): Flow<Int> = merge( flowOf(null), // kick an initial value broadcastReceiverFlow( IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) ), ).map { SubscriptionManager.getDefaultDataSubscriptionId() } .conflate().flowOn(Dispatchers.Default) private fun startToggleSubscriptionDialog( context: Context, subInfo: SubscriptionInfo, newStatus: Boolean ) { SubscriptionUtil.startToggleSubscriptionDialogActivity( context, subInfo.subscriptionId, newStatus ) } private fun startMobileNetworkSettings(context: Context, subInfo: SubscriptionInfo) { MobileNetworkUtils.launchMobileNetworkSettings(context, subInfo) } private fun startAddSimFlow(context: Context) { val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION) intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true) context.startActivity(intent) } private fun showEuiccSettings(context: Context): Boolean { return MobileNetworkUtils.showEuiccSettings(context) } private suspend fun setDefaultVoice( subscriptionManager: SubscriptionManager?, subId: Int): Unit = withContext(Dispatchers.Default) { subscriptionManager?.setDefaultVoiceSubscriptionId(subId) } private suspend fun setDefaultSms( subscriptionManager: SubscriptionManager?, subId: Int): Unit = withContext(Dispatchers.Default) { subscriptionManager?.setDefaultSmsSubId(subId) } private suspend fun setDefaultData(context: Context, subscriptionManager: SubscriptionManager?, wifiPickerTrackerHelper: WifiPickerTrackerHelper?, subId: Int): Unit = withContext(Dispatchers.Default) { subscriptionManager?.setDefaultDataSubId(subId) MobileNetworkUtils.setMobileDataEnabled( context, subId, true /* enabled */, true /* disableOtherSubscriptions */) if (wifiPickerTrackerHelper != null && !wifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(subId)) { wifiPickerTrackerHelper.setCarrierNetworkEnabled(true) } } private suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit = withContext(Dispatchers.Default) { telephonyManager?.setMobileDataPolicyEnabled( TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, newState) //TODO: setup backup calling } No newline at end of file Loading
res/xml/network_provider_internet.xml +0 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,6 @@ settings:keywords="@string/keywords_more_mobile_networks" settings:userRestriction="no_config_mobile_networks" settings:isPreferenceVisible="@bool/config_show_sim_info" settings:allowDividerAbove="true" settings:useAdminDisabledSummary="true" settings:searchable="@bool/config_show_sim_info"/> Loading
src/com/android/settings/network/MobileNetworkListFragment.kt +12 −0 Original line number Diff line number Diff line Loading @@ -26,8 +26,11 @@ import androidx.preference.Preference import com.android.settings.R import com.android.settings.SettingsPreferenceFragment import com.android.settings.dashboard.DashboardFragment import com.android.settings.flags.Flags import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.search.BaseSearchIndexProvider import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.network.NetworkCellularGroupProvider import com.android.settingslib.search.SearchIndexable import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spaprivileged.framework.common.userManager Loading @@ -40,6 +43,15 @@ class MobileNetworkListFragment : DashboardFragment() { collectAirplaneModeAndFinishIfOn() } override fun onCreate(icicle: Bundle?) { super.onCreate(icicle) if (Flags.isDualSimOnboardingEnabled()) { context?.startSpaActivity(NetworkCellularGroupProvider.name); finish() } } override fun onResume() { super.onResume() // Disable the animation of the preference list Loading
src/com/android/settings/network/SubscriptionInfoListViewModel.kt +12 −0 Original line number Diff line number Diff line Loading @@ -32,7 +32,19 @@ class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel application.getSystemService(SubscriptionManager::class.java)!! private val scope = viewModelScope + Dispatchers.Default /** * Getting the active Subscription list */ //ToDo: renaming the function name val subscriptionInfoListFlow = application.subscriptionsChangedFlow().map { SubscriptionUtil.getActiveSubscriptions(subscriptionManager) }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) /** * Getting the Selectable SubscriptionInfo List from the SubscriptionManager's * getAvailableSubscriptionInfoList */ val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map { SubscriptionUtil.getSelectableSubscriptionInfoList(application) }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) }
src/com/android/settings/spa/SettingsSpaEnvironment.kt +2 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import com.android.settings.spa.development.UsageStatsPageProvider import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider import com.android.settings.spa.home.HomePageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider import com.android.settings.spa.network.NetworkCellularGroupProvider import com.android.settings.spa.network.SimOnboardingPageProvider import com.android.settings.spa.notification.AppListNotificationsPageProvider import com.android.settings.spa.notification.NotificationMainPageProvider Loading Loading @@ -118,6 +119,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { ApnEditPageProvider, SimOnboardingPageProvider, BatteryOptimizationModeAppListPageProvider, NetworkCellularGroupProvider, ) override val logger = if (FeatureFlagUtils.isEnabled( Loading
src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt 0 → 100644 +465 −0 Original line number Diff line number Diff line /* * 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.app.Application import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Bundle import android.os.UserManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.euicc.EuiccManager import android.util.Log import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Message import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.DataUsage import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableIntState import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.graphics.vector.ImageVector 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 com.android.settings.R import com.android.settings.network.SubscriptionInfoListViewModel import com.android.settings.network.SubscriptionUtil import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.wifi.WifiPickerTrackerHelper import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spa.widget.preference.ListPreferenceOption import com.android.settingslib.spa.widget.preference.Preference 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.preference.TwoTargetSwitchPreference import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Category import com.android.settingslib.spa.widget.ui.SettingsIcon import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * Showing the sim onboarding which is the process flow of sim switching on. */ object NetworkCellularGroupProvider : SettingsPageProvider { override val name = "NetworkCellularGroupProvider" private lateinit var subscriptionViewModel: SubscriptionInfoListViewModel private val owner = createSettingsPage() var selectableSubscriptionInfoList: List<SubscriptionInfo> = listOf() var defaultVoiceSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var defaultSmsSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var defaultDataSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID var nonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner) .setUiLayoutFn { // never using Preference(object : PreferenceModel { override val title = name override val onClick = navigator(name) }) } @Composable override fun Page(arguments: Bundle?) { val context = LocalContext.current var selectableSubscriptionInfoListRemember = remember { mutableListOf<SubscriptionInfo>().toMutableStateList() } var callsSelectedId = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } var textsSelectedId = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } var mobileDataSelectedId = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } var nonDdsRemember = rememberSaveable { mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID) } subscriptionViewModel = SubscriptionInfoListViewModel( context.applicationContext as Application) allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow) .collectLatestWithLifecycle(LocalLifecycleOwner.current) { selectableSubscriptionInfoListRemember.clear() selectableSubscriptionInfoListRemember.addAll(selectableSubscriptionInfoList) callsSelectedId.intValue = defaultVoiceSubId textsSelectedId.intValue = defaultSmsSubId mobileDataSelectedId.intValue = defaultDataSubId nonDdsRemember.intValue = nonDds } PageImpl(selectableSubscriptionInfoListRemember, callsSelectedId, textsSelectedId, mobileDataSelectedId, nonDdsRemember) } private fun allOfFlows(context: Context, selectableSubscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) = combine( selectableSubscriptionInfoListFlow, context.defaultVoiceSubscriptionFlow(), context.defaultSmsSubscriptionFlow(), context.defaultDefaultDataSubscriptionFlow(), NetworkCellularGroupProvider::refreshUiStates, ).flowOn(Dispatchers.Default) fun refreshUiStates( inputSelectableSubscriptionInfoList: List<SubscriptionInfo>, inputDefaultVoiceSubId: Int, inputDefaultSmsSubId: Int, inputDefaultDateSubId: Int ): Unit { selectableSubscriptionInfoList = inputSelectableSubscriptionInfoList defaultVoiceSubId = inputDefaultVoiceSubId defaultSmsSubId = inputDefaultSmsSubId defaultDataSubId = inputDefaultDateSubId nonDds = if (defaultDataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { SubscriptionManager.INVALID_SUBSCRIPTION_ID } else { selectableSubscriptionInfoList .filter { info -> (info.simSlotIndex != -1) && (info.subscriptionId != defaultDataSubId) } .map { it.subscriptionId } .firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID } } } @Composable fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>, defaultVoiceSubId: MutableIntState, defaultSmsSubId: MutableIntState, defaultDataSubId: MutableIntState, nonDds: MutableIntState) { val context = LocalContext.current var activeSubscriptionInfoList: List<SubscriptionInfo> = selectableSubscriptionInfoList.filter { subscriptionInfo -> subscriptionInfo.simSlotIndex != -1 } var subscriptionManager = context.getSystemService(SubscriptionManager::class.java) val stringSims = stringResource(R.string.provider_network_settings_title) RegularScaffold(title = stringSims) { SimsSectionImpl( context, subscriptionManager, selectableSubscriptionInfoList ) PrimarySimSectionImpl( subscriptionManager, activeSubscriptionInfoList, defaultVoiceSubId, defaultSmsSubId, defaultDataSubId, nonDds ) } } @Composable fun SimsSectionImpl( context: Context, subscriptionManager: SubscriptionManager?, subscriptionInfoList: List<SubscriptionInfo> ) { val coroutineScope = rememberCoroutineScope() for (subInfo in subscriptionInfoList) { val checked = rememberSaveable() { mutableStateOf(false) } //TODO: Add the Restricted TwoTargetSwitchPreference in SPA TwoTargetSwitchPreference(remember { object : SwitchPreferenceModel { override val title = subInfo.displayName.toString() override val summary = { subInfo.number } override val checked = { coroutineScope.launch { withContext(Dispatchers.Default) { checked.value = subscriptionManager?.isSubscriptionEnabled( subInfo.subscriptionId)?:false } } checked.value } override val onCheckedChange = { newChecked: Boolean -> startToggleSubscriptionDialog(context, subInfo, newChecked) } } }) { startMobileNetworkSettings(context, subInfo) } } // + add sim if (showEuiccSettings(context)) { RestrictedPreference( model = object : PreferenceModel { override val title = stringResource(id = R.string.mobile_network_list_add_more) override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) } override val onClick = { startAddSimFlow(context) } }, restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)), ) } } @Composable fun PrimarySimSectionImpl( subscriptionManager: SubscriptionManager?, activeSubscriptionInfoList: List<SubscriptionInfo>, callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, nonDds: MutableIntState ) { var state = rememberSaveable { mutableStateOf(false) } var callsAndSmsList = remember { mutableListOf(ListPreferenceOption(id = -1, text = "Loading")) } var dataList = remember { mutableListOf(ListPreferenceOption(id = -1, text = "Loading")) } if (activeSubscriptionInfoList.size >= 2) { state.value = true callsAndSmsList.clear() dataList.clear() for (info in activeSubscriptionInfoList) { var item = ListPreferenceOption( id = info.subscriptionId, text = "${info.displayName}" ) callsAndSmsList.add(item) dataList.add(item) } callsAndSmsList.add(ListPreferenceOption( id = SubscriptionManager.INVALID_SUBSCRIPTION_ID, text = stringResource(id = R.string.sim_calls_ask_first_prefs_title) )) } else { // hide the primary sim state.value = false Log.d("NetworkCellularGroupProvider", "Hide primary sim") } if (state.value) { val coroutineScope = rememberCoroutineScope() var context = LocalContext.current val telephonyManagerForNonDds: TelephonyManager? = context.getSystemService(TelephonyManager::class.java) ?.createForSubscriptionId(nonDds.intValue) val automaticDataChecked = rememberSaveable() { mutableStateOf(false) } Category(title = stringResource(id = R.string.primary_sim_title)) { createPrimarySimListPreference( stringResource(id = R.string.primary_sim_calls_title), callsAndSmsList, callsSelectedId, ImageVector.vectorResource(R.drawable.ic_phone), ) { callsSelectedId.intValue = it coroutineScope.launch { setDefaultVoice(subscriptionManager, it) } } createPrimarySimListPreference( stringResource(id = R.string.primary_sim_texts_title), callsAndSmsList, textsSelectedId, Icons.AutoMirrored.Outlined.Message, ) { textsSelectedId.intValue = it coroutineScope.launch { setDefaultSms(subscriptionManager, it) } } createPrimarySimListPreference( stringResource(id = R.string.mobile_data_settings_title), dataList, mobileDataSelectedId, Icons.Outlined.DataUsage, ) { mobileDataSelectedId.intValue = it coroutineScope.launch { // TODO: to fix the WifiPickerTracker crash when create // the wifiPickerTrackerHelper setDefaultData(context, subscriptionManager, null/*wifiPickerTrackerHelper*/, it) } } } val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title) val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg) SwitchPreference(remember { object : SwitchPreferenceModel { override val title = autoDataTitle override val summary = { autoDataSummary } override val changeable: () -> Boolean = { nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID } override val checked = { coroutineScope.launch { withContext(Dispatchers.Default) { automaticDataChecked.value = telephonyManagerForNonDds != null && telephonyManagerForNonDds.isMobileDataPolicyEnabled( TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH) } } automaticDataChecked.value } override val onCheckedChange: ((Boolean) -> Unit)? = { newChecked: Boolean -> coroutineScope.launch { setAutomaticData(telephonyManagerForNonDds, newChecked) } } } }) } } private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> = merge( flowOf(null), // kick an initial value broadcastReceiverFlow( IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED) ), ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() } .conflate().flowOn(Dispatchers.Default) private fun Context.defaultSmsSubscriptionFlow(): Flow<Int> = merge( flowOf(null), // kick an initial value broadcastReceiverFlow( IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED) ), ).map { SubscriptionManager.getDefaultSmsSubscriptionId() } .conflate().flowOn(Dispatchers.Default) private fun Context.defaultDefaultDataSubscriptionFlow(): Flow<Int> = merge( flowOf(null), // kick an initial value broadcastReceiverFlow( IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) ), ).map { SubscriptionManager.getDefaultDataSubscriptionId() } .conflate().flowOn(Dispatchers.Default) private fun startToggleSubscriptionDialog( context: Context, subInfo: SubscriptionInfo, newStatus: Boolean ) { SubscriptionUtil.startToggleSubscriptionDialogActivity( context, subInfo.subscriptionId, newStatus ) } private fun startMobileNetworkSettings(context: Context, subInfo: SubscriptionInfo) { MobileNetworkUtils.launchMobileNetworkSettings(context, subInfo) } private fun startAddSimFlow(context: Context) { val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION) intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true) context.startActivity(intent) } private fun showEuiccSettings(context: Context): Boolean { return MobileNetworkUtils.showEuiccSettings(context) } private suspend fun setDefaultVoice( subscriptionManager: SubscriptionManager?, subId: Int): Unit = withContext(Dispatchers.Default) { subscriptionManager?.setDefaultVoiceSubscriptionId(subId) } private suspend fun setDefaultSms( subscriptionManager: SubscriptionManager?, subId: Int): Unit = withContext(Dispatchers.Default) { subscriptionManager?.setDefaultSmsSubId(subId) } private suspend fun setDefaultData(context: Context, subscriptionManager: SubscriptionManager?, wifiPickerTrackerHelper: WifiPickerTrackerHelper?, subId: Int): Unit = withContext(Dispatchers.Default) { subscriptionManager?.setDefaultDataSubId(subId) MobileNetworkUtils.setMobileDataEnabled( context, subId, true /* enabled */, true /* disableOtherSubscriptions */) if (wifiPickerTrackerHelper != null && !wifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(subId)) { wifiPickerTrackerHelper.setCarrierNetworkEnabled(true) } } private suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit = withContext(Dispatchers.Default) { telephonyManager?.setMobileDataPolicyEnabled( TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, newState) //TODO: setup backup calling } No newline at end of file