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