• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.spa.network
18 
19 import android.app.settings.SettingsEnums
20 import android.content.Context
21 import android.content.IntentFilter
22 import android.os.Bundle
23 import android.provider.Settings
24 import android.telephony.SubscriptionInfo
25 import android.telephony.SubscriptionManager
26 import android.telephony.TelephonyManager
27 import android.util.Log
28 import androidx.compose.material.icons.Icons
29 import androidx.compose.material.icons.automirrored.outlined.Message
30 import androidx.compose.material.icons.outlined.DataUsage
31 import androidx.compose.runtime.Composable
32 import androidx.compose.runtime.LaunchedEffect
33 import androidx.compose.runtime.MutableIntState
34 import androidx.compose.runtime.getValue
35 import androidx.compose.runtime.mutableIntStateOf
36 import androidx.compose.runtime.mutableStateOf
37 import androidx.compose.runtime.remember
38 import androidx.compose.runtime.rememberCoroutineScope
39 import androidx.compose.runtime.saveable.rememberSaveable
40 import androidx.compose.ui.graphics.vector.ImageVector
41 import androidx.compose.ui.platform.LocalContext
42 import androidx.compose.ui.res.stringResource
43 import androidx.compose.ui.res.vectorResource
44 import androidx.lifecycle.LifecycleRegistry
45 import androidx.lifecycle.compose.LocalLifecycleOwner
46 import androidx.lifecycle.compose.collectAsStateWithLifecycle
47 import androidx.lifecycle.viewmodel.compose.viewModel
48 import com.android.settings.R
49 import com.android.settings.flags.Flags
50 import com.android.settings.network.SubscriptionInfoListViewModel
51 import com.android.settings.network.telephony.DataSubscriptionRepository
52 import com.android.settings.network.telephony.MobileDataRepository
53 import com.android.settings.network.telephony.SimRepository
54 import com.android.settings.network.telephony.requireSubscriptionManager
55 import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo
56 import com.android.settings.spa.search.SearchablePage
57 import com.android.settings.wifi.WifiPickerTrackerHelper
58 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
59 import com.android.settingslib.spa.framework.common.SettingsPageProvider
60 import com.android.settingslib.spa.framework.common.createSettingsPage
61 import com.android.settingslib.spa.framework.compose.navigator
62 import com.android.settingslib.spa.framework.compose.rememberContext
63 import com.android.settingslib.spa.widget.preference.Preference
64 import com.android.settingslib.spa.widget.preference.PreferenceModel
65 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
66 import com.android.settingslib.spa.widget.ui.Category
67 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
68 import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
69 import kotlinx.coroutines.CoroutineScope
70 import kotlinx.coroutines.Dispatchers
71 import kotlinx.coroutines.flow.Flow
72 import kotlinx.coroutines.flow.combine
73 import kotlinx.coroutines.flow.conflate
74 import kotlinx.coroutines.flow.flowOf
75 import kotlinx.coroutines.flow.flowOn
76 import kotlinx.coroutines.flow.map
77 import kotlinx.coroutines.flow.merge
78 import kotlinx.coroutines.launch
79 import kotlinx.coroutines.withContext
80 
81 /**
82  * Showing the sim onboarding which is the process flow of sim switching on.
83  */
84 open class NetworkCellularGroupProvider : SettingsPageProvider, SearchablePage {
85     override val name = fileName
86     override val metricsCategory = SettingsEnums.MOBILE_NETWORK_LIST
87     private val owner = createSettingsPage()
88 
89     var defaultVoiceSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
90     var defaultSmsSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
91     var defaultDataSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
92     var nonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
93 
94     open fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
95             .setUiLayoutFn {
96                 // never using
97                 Preference(object : PreferenceModel {
98                     override val title = name
99                     override val onClick = navigator(name)
100                 })
101             }
102 
103     @Composable
104     override fun Page(arguments: Bundle?) {
105         val context = LocalContext.current
106         var callsSelectedId = rememberSaveable {
107             mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
108         }
109         var textsSelectedId = rememberSaveable {
110             mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
111         }
112         val mobileDataSelectedId = rememberSaveable { mutableStateOf<Int?>(null) }
113         var nonDdsRemember = rememberSaveable {
114             mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
115         }
116         val subscriptionViewModel = viewModel<SubscriptionInfoListViewModel>()
117 
118         CollectAirplaneModeAndFinishIfOn()
119 
120         LaunchedEffect(Unit) {
121             allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow).collect {
122                 callsSelectedId.intValue = defaultVoiceSubId
123                 textsSelectedId.intValue = defaultSmsSubId
124                 mobileDataSelectedId.value = defaultDataSubId
125                 nonDdsRemember.intValue = nonDds
126             }
127         }
128 
129         val selectableSubscriptionInfoList by subscriptionViewModel
130                 .selectableSubscriptionInfoListFlow
131                 .collectAsStateWithLifecycle(initialValue = emptyList())
132 
133         RegularScaffold(title = stringResource(R.string.provider_network_settings_title)) {
134             SimsSection(selectableSubscriptionInfoList)
135             val mobileDataSelectedIdValue = mobileDataSelectedId.value
136             // Avoid draw mobile data UI before data ready to reduce flaky
137             if (mobileDataSelectedIdValue != null) {
138                 val showMobileDataSection =
139                     selectableSubscriptionInfoList.any { subInfo -> subInfo.simSlotIndex > -1 }
140                 if (showMobileDataSection) {
141                     MobileDataSectionImpl(mobileDataSelectedIdValue, nonDdsRemember.intValue)
142                 }
143 
144                 PrimarySimSectionImpl(
145                     subscriptionViewModel.selectableSubscriptionInfoListFlow,
146                     callsSelectedId,
147                     textsSelectedId,
148                     remember(mobileDataSelectedIdValue) {
149                         mutableIntStateOf(mobileDataSelectedIdValue)
150                     },
151                 )
152             }
153 
154             OtherSection()
155         }
156     }
157 
158     private fun allOfFlows(context: Context,
159                            selectableSubscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) =
160             combine(
161                     selectableSubscriptionInfoListFlow,
162                     context.defaultVoiceSubscriptionFlow(),
163                     context.defaultSmsSubscriptionFlow(),
164                     DataSubscriptionRepository(context).defaultDataSubscriptionIdFlow(),
165                     this::refreshUiStates,
166             ).flowOn(Dispatchers.Default)
167 
168     private fun refreshUiStates(
169         selectableSubscriptionInfoList: List<SubscriptionInfo>,
170         inputDefaultVoiceSubId: Int,
171         inputDefaultSmsSubId: Int,
172         inputDefaultDateSubId: Int
173     ) {
174         defaultVoiceSubId = inputDefaultVoiceSubId
175         defaultSmsSubId = inputDefaultSmsSubId
176         defaultDataSubId = inputDefaultDateSubId
177         nonDds = if (defaultDataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
178             SubscriptionManager.INVALID_SUBSCRIPTION_ID
179         } else {
180             selectableSubscriptionInfoList
181                     .filter { info ->
182                         (info.simSlotIndex != -1) && (info.subscriptionId != defaultDataSubId)
183                     }
184                     .map { it.subscriptionId }
185                     .firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
186         }
187 
188         Log.d(name, "defaultDataSubId: $defaultDataSubId, nonDds: $nonDds")
189     }
190     @Composable
191     open fun OtherSection(){
192         // Do nothing
193     }
194 
195     override fun getPageTitleForSearch(context: Context): String =
196         context.getString(R.string.provider_network_settings_title)
197 
198     override fun getSearchableTitles(context: Context): List<String> {
199         if (!isPageSearchable(context)) return emptyList()
200         return buildList {
201             if (context.requireSubscriptionManager().activeSubscriptionInfoCount > 0) {
202                 add(context.getString(R.string.mobile_data_settings_title))
203             }
204         }
205     }
206 
207     companion object {
208         const val fileName = "NetworkCellularGroupProvider"
209 
210         private fun isPageSearchable(context: Context) =
211             Flags.isDualSimOnboardingEnabled() && SimRepository(context).canEnterMobileNetworkPage()
212     }
213 }
214 
215 @Composable
MobileDataSectionImplnull216 fun MobileDataSectionImpl(mobileDataSelectedId: Int, nonDds: Int) {
217     val mobileDataRepository = rememberContext(::MobileDataRepository)
218 
219     Category(title = stringResource(id = R.string.mobile_data_settings_title)) {
220         MobileDataSwitchPreference(subId = mobileDataSelectedId)
221 
222         val isAutoDataEnabled by remember(nonDds) {
223             mobileDataRepository.isMobileDataPolicyEnabledFlow(
224                 subId = nonDds,
225                 policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH
226             )
227         }.collectAsStateWithLifecycle(initialValue = null)
228         if (SubscriptionManager.isValidSubscriptionId(nonDds)) {
229             AutomaticDataSwitchingPreference(
230                 isAutoDataEnabled = { isAutoDataEnabled },
231                 setAutoDataEnabled = { newEnabled ->
232                     mobileDataRepository.setAutoDataSwitch(nonDds, newEnabled)
233                 },
234             )
235         }
236     }
237 }
238 
239 @Composable
PrimarySimImplnull240 fun PrimarySimImpl(
241     primarySimInfo: PrimarySimInfo,
242     callsSelectedId: MutableIntState,
243     textsSelectedId: MutableIntState,
244     mobileDataSelectedId: MutableIntState,
245     wifiPickerTrackerHelper: WifiPickerTrackerHelper? = null,
246     subscriptionManager: SubscriptionManager? =
247         LocalContext.current.getSystemService(SubscriptionManager::class.java),
248     coroutineScope: CoroutineScope = rememberCoroutineScope(),
249     context: Context = LocalContext.current,
250     actionSetCalls: (Int) -> Unit = {
251         callsSelectedId.intValue = it
252         coroutineScope.launch {
253             setDefaultVoice(subscriptionManager, it)
254         }
255     },
<lambda>null256     actionSetTexts: (Int) -> Unit = {
257         textsSelectedId.intValue = it
258         coroutineScope.launch {
259             setDefaultSms(subscriptionManager, it)
260         }
261     },
<lambda>null262     actionSetMobileData: (Int) -> Unit = {
263         coroutineScope.launch {
264             setDefaultData(
265                 context,
266                 subscriptionManager,
267                 wifiPickerTrackerHelper,
268                 it
269             )
270         }
271     },
272 ) {
273     CreatePrimarySimListPreference(
274         stringResource(id = R.string.primary_sim_calls_title),
275         primarySimInfo.callsList,
276         callsSelectedId,
277         ImageVector.vectorResource(R.drawable.ic_phone),
278         actionSetCalls
279     )
280     CreatePrimarySimListPreference(
281         stringResource(id = R.string.primary_sim_texts_title),
282         primarySimInfo.smsList,
283         textsSelectedId,
284         Icons.AutoMirrored.Outlined.Message,
285         actionSetTexts
286     )
287     CreatePrimarySimListPreference(
288         stringResource(id = R.string.mobile_data_settings_title),
289         primarySimInfo.dataList,
290         mobileDataSelectedId,
291         Icons.Outlined.DataUsage,
292         actionSetMobileData
293     )
294 }
295 
296 @Composable
PrimarySimSectionImplnull297 fun PrimarySimSectionImpl(
298     subscriptionInfoListFlow: Flow<List<SubscriptionInfo>>,
299     callsSelectedId: MutableIntState,
300     textsSelectedId: MutableIntState,
301     mobileDataSelectedId: MutableIntState,
302 ) {
303     val context = LocalContext.current
304     val primarySimInfo = remember(subscriptionInfoListFlow) {
305         subscriptionInfoListFlow
306             .map { subscriptionInfoList ->
307                 subscriptionInfoList.filter { subInfo -> subInfo.simSlotIndex != -1 }
308             }
309             .map(PrimarySimRepository(context)::getPrimarySimInfo)
310             .flowOn(Dispatchers.Default)
311     }.collectAsStateWithLifecycle(initialValue = null).value ?: return
312 
313     Category(title = stringResource(id = R.string.primary_sim_title)) {
314         PrimarySimImpl(
315             primarySimInfo,
316             callsSelectedId,
317             textsSelectedId,
318             mobileDataSelectedId,
319             rememberWifiPickerTrackerHelper()
320         )
321     }
322 }
323 
324 @Composable
CollectAirplaneModeAndFinishIfOnnull325 fun CollectAirplaneModeAndFinishIfOn() {
326     val context = LocalContext.current
327     LaunchedEffect(Unit) {
328         context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON).collect {
329             isAirplaneModeOn ->
330             if (isAirplaneModeOn) {
331                 context.getActivity()?.finish()
332             }
333         }
334     }
335 }
336 
337 @Composable
rememberWifiPickerTrackerHelpernull338 fun rememberWifiPickerTrackerHelper(): WifiPickerTrackerHelper {
339     val context = LocalContext.current
340     val lifecycleOwner = LocalLifecycleOwner.current
341     return remember { WifiPickerTrackerHelper(LifecycleRegistry(lifecycleOwner), context, null) }
342 }
343 
Contextnull344 private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> =
345         merge(
346                 flowOf(null), // kick an initial value
347                 broadcastReceiverFlow(
348                         IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)
349                 ),
350         ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() }
351                 .conflate().flowOn(Dispatchers.Default)
352 
Contextnull353 private fun Context.defaultSmsSubscriptionFlow(): Flow<Int> =
354         merge(
355                 flowOf(null), // kick an initial value
356                 broadcastReceiverFlow(
357                         IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED)
358                 ),
359         ).map { SubscriptionManager.getDefaultSmsSubscriptionId() }
360                 .conflate().flowOn(Dispatchers.Default)
361 
setDefaultVoicenull362 suspend fun setDefaultVoice(
363     subscriptionManager: SubscriptionManager?,
364     subId: Int
365 ): Unit =
366     withContext(Dispatchers.Default) {
367         subscriptionManager?.setDefaultVoiceSubscriptionId(subId)
368     }
369 
setDefaultSmsnull370 suspend fun setDefaultSms(
371     subscriptionManager: SubscriptionManager?,
372     subId: Int
373 ): Unit =
374     withContext(Dispatchers.Default) {
375         subscriptionManager?.setDefaultSmsSubId(subId)
376     }
377 
setDefaultDatanull378 suspend fun setDefaultData(
379     context: Context,
380     subscriptionManager: SubscriptionManager?,
381     wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
382     subId: Int
383 ): Unit =
384     setMobileData(
385         context,
386         subscriptionManager,
387         wifiPickerTrackerHelper,
388         subId,
389         true
390     )
391 
392 suspend fun setMobileData(
393     context: Context,
394     subscriptionManager: SubscriptionManager?,
395     wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
396     subId: Int,
397     enabled: Boolean,
398 ): Unit =
399     withContext(Dispatchers.Default) {
400         Log.d(NetworkCellularGroupProvider.fileName, "setMobileData[$subId]: $enabled")
401 
402         var targetSubId = subId
403         val activeSubIdList = subscriptionManager?.activeSubscriptionIdList
404         if (activeSubIdList?.size == 1) {
405             targetSubId = activeSubIdList[0]
406             Log.d(
407                 NetworkCellularGroupProvider.fileName,
408                 "There is only one sim in the device, correct dds as $targetSubId"
409             )
410         }
411 
412         if (enabled) {
413             Log.d(NetworkCellularGroupProvider.fileName, "setDefaultData: [$targetSubId]")
414             subscriptionManager?.setDefaultDataSubId(targetSubId)
415         }
416         MobileDataRepository(context)
417             .setMobileDataEnabled(targetSubId, enabled, wifiPickerTrackerHelper)
418     }