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.MutableIntState
33 import androidx.compose.runtime.getValue
34 import androidx.compose.runtime.mutableIntStateOf
35 import androidx.compose.runtime.mutableStateOf
36 import androidx.compose.runtime.remember
37 import androidx.compose.runtime.rememberCoroutineScope
38 import androidx.compose.runtime.saveable.rememberSaveable
39 import androidx.compose.ui.graphics.vector.ImageVector
40 import androidx.compose.ui.platform.LocalContext
41 import androidx.compose.ui.res.stringResource
42 import androidx.compose.ui.res.vectorResource
43 import androidx.lifecycle.LifecycleOwner
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.network.SubscriptionInfoListViewModel
50 import com.android.settings.network.telephony.DataSubscriptionRepository
51 import com.android.settings.network.telephony.TelephonyRepository
52 import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo
53 import com.android.settings.wifi.WifiPickerTrackerHelper
54 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
55 import com.android.settingslib.spa.framework.common.SettingsPageProvider
56 import com.android.settingslib.spa.framework.common.createSettingsPage
57 import com.android.settingslib.spa.framework.compose.navigator
58 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
59 import com.android.settingslib.spa.widget.preference.Preference
60 import com.android.settingslib.spa.widget.preference.PreferenceModel
61 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
62 import com.android.settingslib.spa.widget.ui.Category
63 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
64 import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
65 import kotlinx.coroutines.CoroutineScope
66 import kotlinx.coroutines.Dispatchers
67 import kotlinx.coroutines.flow.Flow
68 import kotlinx.coroutines.flow.combine
69 import kotlinx.coroutines.flow.conflate
70 import kotlinx.coroutines.flow.flowOf
71 import kotlinx.coroutines.flow.flowOn
72 import kotlinx.coroutines.flow.map
73 import kotlinx.coroutines.flow.merge
74 import kotlinx.coroutines.launch
75 import kotlinx.coroutines.withContext
76
77 /**
78 * Showing the sim onboarding which is the process flow of sim switching on.
79 */
80 open class NetworkCellularGroupProvider : SettingsPageProvider {
81 override val name = fileName
82 override val metricsCategory = SettingsEnums.MOBILE_NETWORK_LIST
83 private val owner = createSettingsPage()
84
85 var defaultVoiceSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
86 var defaultSmsSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
87 var defaultDataSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
88 var nonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
89
90 open fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
91 .setUiLayoutFn {
92 // never using
93 Preference(object : PreferenceModel {
94 override val title = name
95 override val onClick = navigator(name)
96 })
97 }
98
99 @Composable
100 override fun Page(arguments: Bundle?) {
101 val context = LocalContext.current
102 var callsSelectedId = rememberSaveable {
103 mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
104 }
105 var textsSelectedId = rememberSaveable {
106 mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
107 }
108 var mobileDataSelectedId = rememberSaveable {
109 mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
110 }
111 var nonDdsRemember = rememberSaveable {
112 mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
113 }
114 var showMobileDataSection = rememberSaveable {
115 mutableStateOf(false)
116 }
117 val subscriptionViewModel = viewModel<SubscriptionInfoListViewModel>()
118
119 CollectAirplaneModeAndFinishIfOn()
120
121 remember {
122 allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow)
123 }.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
124 callsSelectedId.intValue = defaultVoiceSubId
125 textsSelectedId.intValue = defaultSmsSubId
126 mobileDataSelectedId.intValue = defaultDataSubId
127 nonDdsRemember.intValue = nonDds
128 }
129
130 val selectableSubscriptionInfoList by subscriptionViewModel
131 .selectableSubscriptionInfoListFlow
132 .collectAsStateWithLifecycle(initialValue = emptyList())
133 showMobileDataSection.value = selectableSubscriptionInfoList
134 .filter { subInfo -> subInfo.simSlotIndex > -1 }
135 .size > 0
136 val stringSims = stringResource(R.string.provider_network_settings_title)
137 RegularScaffold(title = stringSims) {
138 SimsSection(selectableSubscriptionInfoList)
139 if(showMobileDataSection.value) {
140 MobileDataSectionImpl(
141 mobileDataSelectedId,
142 nonDdsRemember,
143 )
144 }
145
146 PrimarySimSectionImpl(
147 subscriptionViewModel.selectableSubscriptionInfoListFlow,
148 callsSelectedId,
149 textsSelectedId,
150 mobileDataSelectedId,
151 )
152
153 OtherSection()
154 }
155 }
156
157 private fun allOfFlows(context: Context,
158 selectableSubscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) =
159 combine(
160 selectableSubscriptionInfoListFlow,
161 context.defaultVoiceSubscriptionFlow(),
162 context.defaultSmsSubscriptionFlow(),
163 DataSubscriptionRepository(context).defaultDataSubscriptionIdFlow(),
164 this::refreshUiStates,
165 ).flowOn(Dispatchers.Default)
166
167 private fun refreshUiStates(
168 selectableSubscriptionInfoList: List<SubscriptionInfo>,
169 inputDefaultVoiceSubId: Int,
170 inputDefaultSmsSubId: Int,
171 inputDefaultDateSubId: Int
172 ) {
173 defaultVoiceSubId = inputDefaultVoiceSubId
174 defaultSmsSubId = inputDefaultSmsSubId
175 defaultDataSubId = inputDefaultDateSubId
176 nonDds = if (defaultDataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
177 SubscriptionManager.INVALID_SUBSCRIPTION_ID
178 } else {
179 selectableSubscriptionInfoList
180 .filter { info ->
181 (info.simSlotIndex != -1) && (info.subscriptionId != defaultDataSubId)
182 }
183 .map { it.subscriptionId }
184 .firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
185 }
186
187 Log.d(name, "defaultDataSubId: $defaultDataSubId, nonDds: $nonDds")
188 }
189 @Composable
190 open fun OtherSection(){
191 // Do nothing
192 }
193 companion object {
194 const val fileName = "NetworkCellularGroupProvider"
195 }
196 }
197
198 @Composable
MobileDataSectionImplnull199 fun MobileDataSectionImpl(
200 mobileDataSelectedId: MutableIntState,
201 nonDds: MutableIntState,
202 ) {
203 val context = LocalContext.current
204 val localLifecycleOwner = LocalLifecycleOwner.current
205 val wifiPickerTrackerHelper = getWifiPickerTrackerHelper(context, localLifecycleOwner)
206
207 val subscriptionManager: SubscriptionManager? =
208 context.getSystemService(SubscriptionManager::class.java)
209
210 Category(title = stringResource(id = R.string.mobile_data_settings_title)) {
211 val isAutoDataEnabled by remember(nonDds.intValue) {
212 TelephonyRepository(context).isMobileDataPolicyEnabledFlow(
213 subId = nonDds.intValue,
214 policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH
215 )
216 }.collectAsStateWithLifecycle(initialValue = null)
217
218 val mobileDataStateChanged by remember(mobileDataSelectedId.intValue) {
219 TelephonyRepository(context).isDataEnabledFlow(mobileDataSelectedId.intValue)
220 }.collectAsStateWithLifecycle(initialValue = false)
221 val coroutineScope = rememberCoroutineScope()
222
223 MobileDataSwitchingPreference(
224 isMobileDataEnabled = { mobileDataStateChanged },
225 setMobileDataEnabled = { newEnabled ->
226 coroutineScope.launch {
227 setMobileData(
228 context,
229 subscriptionManager,
230 wifiPickerTrackerHelper,
231 mobileDataSelectedId.intValue,
232 newEnabled
233 )
234 }
235 },
236 )
237 if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
238 AutomaticDataSwitchingPreference(
239 isAutoDataEnabled = { isAutoDataEnabled },
240 setAutoDataEnabled = { newEnabled ->
241 TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled)
242 },
243 )
244 }
245 }
246 }
247
248 @Composable
PrimarySimImplnull249 fun PrimarySimImpl(
250 primarySimInfo: PrimarySimInfo,
251 callsSelectedId: MutableIntState,
252 textsSelectedId: MutableIntState,
253 mobileDataSelectedId: MutableIntState,
254 wifiPickerTrackerHelper: WifiPickerTrackerHelper? = null,
255 subscriptionManager: SubscriptionManager? =
256 LocalContext.current.getSystemService(SubscriptionManager::class.java),
257 coroutineScope: CoroutineScope = rememberCoroutineScope(),
258 context: Context = LocalContext.current,
259 actionSetCalls: (Int) -> Unit = {
260 callsSelectedId.intValue = it
261 coroutineScope.launch {
262 setDefaultVoice(subscriptionManager, it)
263 }
264 },
<lambda>null265 actionSetTexts: (Int) -> Unit = {
266 textsSelectedId.intValue = it
267 coroutineScope.launch {
268 setDefaultSms(subscriptionManager, it)
269 }
270 },
<lambda>null271 actionSetMobileData: (Int) -> Unit = {
272 coroutineScope.launch {
273 setDefaultData(
274 context,
275 subscriptionManager,
276 wifiPickerTrackerHelper,
277 it
278 )
279 }
280 },
281 ) {
282 CreatePrimarySimListPreference(
283 stringResource(id = R.string.primary_sim_calls_title),
284 primarySimInfo.callsAndSmsList,
285 callsSelectedId,
286 ImageVector.vectorResource(R.drawable.ic_phone),
287 actionSetCalls
288 )
289 CreatePrimarySimListPreference(
290 stringResource(id = R.string.primary_sim_texts_title),
291 primarySimInfo.callsAndSmsList,
292 textsSelectedId,
293 Icons.AutoMirrored.Outlined.Message,
294 actionSetTexts
295 )
296 CreatePrimarySimListPreference(
297 stringResource(id = R.string.mobile_data_settings_title),
298 primarySimInfo.dataList,
299 mobileDataSelectedId,
300 Icons.Outlined.DataUsage,
301 actionSetMobileData
302 )
303 }
304
305 @Composable
PrimarySimSectionImplnull306 fun PrimarySimSectionImpl(
307 subscriptionInfoListFlow: Flow<List<SubscriptionInfo>>,
308 callsSelectedId: MutableIntState,
309 textsSelectedId: MutableIntState,
310 mobileDataSelectedId: MutableIntState,
311 ) {
312 val context = LocalContext.current
313 val localLifecycleOwner = LocalLifecycleOwner.current
314 val wifiPickerTrackerHelper = getWifiPickerTrackerHelper(context, localLifecycleOwner)
315
316 val primarySimInfo = remember(subscriptionInfoListFlow) {
317 subscriptionInfoListFlow
318 .map { subscriptionInfoList ->
319 subscriptionInfoList.filter { subInfo -> subInfo.simSlotIndex != -1 }
320 }
321 .map(PrimarySimRepository(context)::getPrimarySimInfo)
322 .flowOn(Dispatchers.Default)
323 }.collectAsStateWithLifecycle(initialValue = null).value ?: return
324
325 Category(title = stringResource(id = R.string.primary_sim_title)) {
326 PrimarySimImpl(
327 primarySimInfo,
328 callsSelectedId,
329 textsSelectedId,
330 mobileDataSelectedId,
331 wifiPickerTrackerHelper
332 )
333 }
334 }
335
336 @Composable
CollectAirplaneModeAndFinishIfOnnull337 fun CollectAirplaneModeAndFinishIfOn() {
338 val context = LocalContext.current
339 context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON)
340 .collectLatestWithLifecycle(LocalLifecycleOwner.current) { isAirplaneModeOn ->
341 if (isAirplaneModeOn) {
342 context.getActivity()?.finish()
343 }
344 }
345 }
346
getWifiPickerTrackerHelpernull347 private fun getWifiPickerTrackerHelper(
348 context: Context,
349 lifecycleOwner: LifecycleOwner
350 ): WifiPickerTrackerHelper {
351 return WifiPickerTrackerHelper(
352 LifecycleRegistry(lifecycleOwner), context,
353 null /* WifiPickerTrackerCallback */
354 )
355 }
356
Contextnull357 private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> =
358 merge(
359 flowOf(null), // kick an initial value
360 broadcastReceiverFlow(
361 IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)
362 ),
363 ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() }
364 .conflate().flowOn(Dispatchers.Default)
365
Contextnull366 private fun Context.defaultSmsSubscriptionFlow(): Flow<Int> =
367 merge(
368 flowOf(null), // kick an initial value
369 broadcastReceiverFlow(
370 IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED)
371 ),
372 ).map { SubscriptionManager.getDefaultSmsSubscriptionId() }
373 .conflate().flowOn(Dispatchers.Default)
374
setDefaultVoicenull375 suspend fun setDefaultVoice(
376 subscriptionManager: SubscriptionManager?,
377 subId: Int
378 ): Unit =
379 withContext(Dispatchers.Default) {
380 subscriptionManager?.setDefaultVoiceSubscriptionId(subId)
381 }
382
setDefaultSmsnull383 suspend fun setDefaultSms(
384 subscriptionManager: SubscriptionManager?,
385 subId: Int
386 ): Unit =
387 withContext(Dispatchers.Default) {
388 subscriptionManager?.setDefaultSmsSubId(subId)
389 }
390
setDefaultDatanull391 suspend fun setDefaultData(
392 context: Context,
393 subscriptionManager: SubscriptionManager?,
394 wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
395 subId: Int
396 ): Unit =
397 setMobileData(
398 context,
399 subscriptionManager,
400 wifiPickerTrackerHelper,
401 subId,
402 true
403 )
404
405 suspend fun setMobileData(
406 context: Context,
407 subscriptionManager: SubscriptionManager?,
408 wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
409 subId: Int,
410 enabled: Boolean,
411 ): Unit =
412 withContext(Dispatchers.Default) {
413 Log.d(NetworkCellularGroupProvider.fileName, "setMobileData[$subId]: $enabled")
414
415 var targetSubId = subId
416 val activeSubIdList = subscriptionManager?.activeSubscriptionIdList
417 if (activeSubIdList?.size == 1) {
418 targetSubId = activeSubIdList[0]
419 Log.d(
420 NetworkCellularGroupProvider.fileName,
421 "There is only one sim in the device, correct dds as $targetSubId"
422 )
423 }
424
425 if (enabled) {
426 Log.d(NetworkCellularGroupProvider.fileName, "setDefaultData: [$targetSubId]")
427 subscriptionManager?.setDefaultDataSubId(targetSubId)
428 }
429 TelephonyRepository(context)
430 .setMobileData(targetSubId, enabled, wifiPickerTrackerHelper)
431 }