1 /*
<lambda>null2 * Copyright (C) 2023 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.network
18
19 import android.content.Context
20 import android.content.IntentFilter
21 import android.telephony.SubscriptionInfo
22 import android.telephony.SubscriptionManager
23 import android.telephony.TelephonyManager
24 import androidx.annotation.VisibleForTesting
25 import androidx.fragment.app.Fragment
26 import androidx.fragment.app.viewModels
27 import androidx.lifecycle.LifecycleOwner
28 import androidx.preference.PreferenceScreen
29 import com.android.settings.R
30 import com.android.settings.core.BasePreferenceController
31 import com.android.settings.flags.Flags
32 import com.android.settingslib.RestrictedPreference
33 import com.android.settingslib.Utils
34 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
35 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
36 import com.android.settingslib.spaprivileged.framework.common.userManager
37 import kotlinx.coroutines.Dispatchers
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.combine
40 import kotlinx.coroutines.flow.conflate
41 import kotlinx.coroutines.flow.flowOf
42 import kotlinx.coroutines.flow.flowOn
43 import kotlinx.coroutines.flow.map
44 import kotlinx.coroutines.flow.merge
45
46 /**
47 * The summary text and click behavior of the "Calls & SMS" item on the Network & internet page.
48 */
49 open class NetworkProviderCallsSmsController @JvmOverloads constructor(
50 context: Context,
51 preferenceKey: String,
52 private val getDisplayName: (SubscriptionInfo) -> CharSequence = { subInfo ->
53 SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context)
54 },
55 private val isInService: (Int) -> Boolean = IsInServiceImpl(context)::isInService,
56 ) : BasePreferenceController(context, preferenceKey) {
57
58 private lateinit var lazyViewModel: Lazy<SubscriptionInfoListViewModel>
59 private lateinit var preference: RestrictedPreference
60
initnull61 fun init(fragment: Fragment) {
62 lazyViewModel = fragment.viewModels()
63 }
64
getAvailabilityStatusnull65 override fun getAvailabilityStatus() = when {
66 Flags.isDualSimOnboardingEnabled() -> UNSUPPORTED_ON_DEVICE
67 !SubscriptionUtil.isSimHardwareVisible(mContext) -> UNSUPPORTED_ON_DEVICE
68 !mContext.userManager.isAdminUser -> DISABLED_FOR_USER
69 else -> AVAILABLE
70 }
71
displayPreferencenull72 override fun displayPreference(screen: PreferenceScreen) {
73 super.displayPreference(screen)
74 preference = screen.findPreference(preferenceKey)!!
75 }
76
onViewCreatednull77 override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
78 val viewModel by lazyViewModel
79
80 summaryFlow(viewModel.subscriptionInfoListFlow)
81 .collectLatestWithLifecycle(viewLifecycleOwner) { preference.summary = it }
82
83 viewModel.subscriptionInfoListFlow
84 .collectLatestWithLifecycle(viewLifecycleOwner) { subscriptionInfoList ->
85 if (!preference.isDisabledByAdmin) {
86 preference.isEnabled = subscriptionInfoList.isNotEmpty()
87 }
88 }
89 }
90
summaryFlownull91 private fun summaryFlow(subscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) = combine(
92 subscriptionInfoListFlow,
93 mContext.defaultVoiceSubscriptionFlow(),
94 mContext.defaultSmsSubscriptionFlow(),
95 ::getSummary,
96 ).flowOn(Dispatchers.Default)
97
98 @VisibleForTesting
99 fun getSummary(
100 activeSubscriptionInfoList: List<SubscriptionInfo>,
101 defaultVoiceSubscriptionId: Int,
102 defaultSmsSubscriptionId: Int,
103 ): String {
104 if (activeSubscriptionInfoList.isEmpty()) {
105 return mContext.getString(R.string.calls_sms_no_sim)
106 }
107
108 activeSubscriptionInfoList.singleOrNull()?.let {
109 // Set displayName as summary if there is only one valid SIM.
110 if (isInService(it.subscriptionId)) return it.displayName.toString()
111 }
112
113 return activeSubscriptionInfoList.joinToString { subInfo ->
114 val displayName = getDisplayName(subInfo)
115
116 val subId = subInfo.subscriptionId
117 val statusResId = getPreferredStatus(
118 subId = subId,
119 subsSize = activeSubscriptionInfoList.size,
120 isCallPreferred = subId == defaultVoiceSubscriptionId,
121 isSmsPreferred = subId == defaultSmsSubscriptionId,
122 )
123 if (statusResId == null) {
124 // If there are 2 or more SIMs and one of these has no preferred status,
125 // set only its displayName as summary.
126 displayName
127 } else {
128 "$displayName (${mContext.getString(statusResId)})"
129 }
130 }
131 }
132
getPreferredStatusnull133 private fun getPreferredStatus(
134 subId: Int,
135 subsSize: Int,
136 isCallPreferred: Boolean,
137 isSmsPreferred: Boolean,
138 ): Int? = when {
139 !isInService(subId) -> {
140 if (subsSize > 1) {
141 R.string.calls_sms_unavailable
142 } else {
143 R.string.calls_sms_temp_unavailable
144 }
145 }
146
147 isCallPreferred && isSmsPreferred -> R.string.calls_sms_preferred
148 isCallPreferred -> R.string.calls_sms_calls_preferred
149 isSmsPreferred -> R.string.calls_sms_sms_preferred
150 else -> null
151 }
152 }
153
Contextnull154 private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> =
155 merge(
156 flowOf(null), // kick an initial value
157 broadcastReceiverFlow(
158 IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)
159 ),
160 ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() }
161 .conflate().flowOn(Dispatchers.Default)
162
Contextnull163 private fun Context.defaultSmsSubscriptionFlow(): Flow<Int> =
164 merge(
165 flowOf(null), // kick an initial value
166 broadcastReceiverFlow(
167 IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED)
168 ),
169 ).map { SubscriptionManager.getDefaultSmsSubscriptionId() }
170 .conflate().flowOn(Dispatchers.Default)
171
172 private class IsInServiceImpl(context: Context) {
173 private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
174
isInServicenull175 fun isInService(subId: Int): Boolean {
176 if (!SubscriptionManager.isValidSubscriptionId(subId)) return false
177
178 val serviceState = telephonyManager.createForSubscriptionId(subId).serviceState
179 return Utils.isInService(serviceState)
180 }
181 }
182