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.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
20 import android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX
21 
22 import android.content.Context
23 import android.telephony.SubscriptionInfo
24 import android.telephony.SubscriptionManager
25 import android.telephony.TelephonyManager
26 import android.telephony.UiccCardInfo
27 import android.telephony.UiccSlotInfo
28 import android.util.Log
29 import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
30 import com.android.settings.network.telephony.TelephonyRepository
31 import com.android.settings.sim.SimActivationNotifier
32 import com.android.settings.spa.network.setAutomaticData
33 import com.android.settings.spa.network.setDefaultData
34 import com.android.settings.spa.network.setDefaultSms
35 import com.android.settings.spa.network.setDefaultVoice
36 import com.android.settings.wifi.WifiPickerTrackerHelper
37 import com.android.settingslib.utils.ThreadUtils
38 import kotlinx.coroutines.Dispatchers
39 import kotlinx.coroutines.flow.MutableStateFlow
40 import kotlinx.coroutines.withContext
41 
42 class SimOnboardingService {
43     var subscriptionManager:SubscriptionManager? = null
44     var telephonyManager:TelephonyManager? = null
45 
46     var targetSubId: Int = INVALID_SUBSCRIPTION_ID
47     var targetSubInfo: SubscriptionInfo? = null
48     var availableSubInfoList: List<SubscriptionInfo> = listOf()
49     var activeSubInfoList: List<SubscriptionInfo> = listOf()
50     var slotInfoList: List<UiccSlotInfo> = listOf()
51     var uiccCardInfoList: List<UiccCardInfo> = listOf()
52     var targetPrimarySimCalls: Int = INVALID_SUBSCRIPTION_ID
53     var targetPrimarySimTexts: Int = INVALID_SUBSCRIPTION_ID
54     var targetPrimarySimMobileData: Int = INVALID_SUBSCRIPTION_ID
55     val targetPrimarySimAutoDataSwitch = MutableStateFlow(false)
56     var targetNonDds: Int = INVALID_SUBSCRIPTION_ID
57         get() {
58             if(targetPrimarySimMobileData == INVALID_SUBSCRIPTION_ID) {
59                 Log.w(TAG, "No DDS")
60                 return INVALID_SUBSCRIPTION_ID
61             }
62             return userSelectedSubInfoList
63                 .filter { info -> info.subscriptionId != targetPrimarySimMobileData }
64                 .map { it.subscriptionId }
65                 .firstOrNull() ?: INVALID_SUBSCRIPTION_ID
66         }
67     var callback: (CallbackType) -> Unit = {}
68 
69     var isMultipleEnabledProfilesSupported: Boolean = false
70         get() {
71             if (uiccCardInfoList.isEmpty()) {
72                 Log.w(TAG, "UICC cards info list is empty.")
73                 return false
74             }
75             return  uiccCardInfoList.any { it.isMultipleEnabledProfilesSupported }
76         }
77     var isRemovablePsimProfileEnabled: Boolean = false
78         get() {
79             if(slotInfoList.isEmpty()) {
80                 Log.w(TAG, "UICC Slot info list is empty.")
81                 return false
82             }
83             return UiccSlotUtil.isRemovableSimEnabled(slotInfoList)
84         }
85     var isEsimProfileEnabled: Boolean = false
86         get() {
87             activeSubInfoList.stream().anyMatch { it.isEmbedded }
88             return false
89         }
90     var doesTargetSimActive = false
91         get() {
92             return targetSubInfo?.getSimSlotIndex() ?: INVALID_SIM_SLOT_INDEX >= 0
93         }
94 
95     var doesTargetSimHaveEsimOperation = false
96         get() {
97             return targetSubInfo?.isEmbedded ?: false
98         }
99 
100     var isUsableTargetSubscriptionId = false
101         get() {
102             return SubscriptionManager.isUsableSubscriptionId(targetSubId)
103         }
104     var getActiveModemCount = 0
105         get() {
106             return (telephonyManager?.getActiveModemCount() ?: 0)
107         }
108 
109     var renameMutableMap : MutableMap<Int, String> = mutableMapOf()
110     var userSelectedSubInfoList : MutableList<SubscriptionInfo> = mutableListOf()
111 
112     var isSimSelectionFinished = false
113         get() {
114             val activeModem = getActiveModemCount
115             return activeModem != 0 && userSelectedSubInfoList.size == activeModem
116         }
117 
118     var isAllOfSlotAssigned = false
119         get() {
120             val activeModem = getActiveModemCount
121             if(activeModem == 0){
122                 Log.e(TAG, "isAllOfSlotAssigned: getActiveModemCount is 0")
123                 return true
124             }
125             return getActiveModemCount != 0 && activeSubInfoList.size == activeModem
126         }
127     var isMultiSimEnabled = false
128         get() {
129             return getActiveModemCount > 1
130         }
131     var isMultiSimSupported = false
132         get() {
133             return telephonyManager?.isMultiSimSupported == TelephonyManager.MULTISIM_ALLOWED
134         }
135 
136     var doesSwitchMultiSimConfigTriggerReboot = false
137         get() {
138             return telephonyManager?.doesSwitchMultiSimConfigTriggerReboot() ?: false
139         }
140 
141     fun isValid(): Boolean {
142         return targetSubId != INVALID_SUBSCRIPTION_ID
143             && targetSubInfo != null
144             && activeSubInfoList.isNotEmpty()
145             && slotInfoList.isNotEmpty()
146     }
147 
148     fun clear() {
149         targetSubId = -1
150         targetSubInfo = null
151         availableSubInfoList = listOf()
152         activeSubInfoList = listOf()
153         slotInfoList = listOf()
154         uiccCardInfoList = listOf()
155         targetPrimarySimCalls = -1
156         targetPrimarySimTexts = -1
157         targetPrimarySimMobileData = -1
158         clearUserRecord()
159     }
160 
161     fun clearUserRecord(){
162         renameMutableMap.clear()
163         userSelectedSubInfoList.clear()
164     }
165 
166     fun initData(inputTargetSubId: Int,
167                  context: Context,
168                  callback: (CallbackType) -> Unit) {
169         clear()
170         this.callback = callback
171         targetSubId = inputTargetSubId
172         subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
173         telephonyManager = context.getSystemService(TelephonyManager::class.java)
174         activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
175         Log.d(
176             TAG, "startInit: targetSubId:$targetSubId, activeSubInfoList: $activeSubInfoList"
177         )
178 
179         ThreadUtils.postOnBackgroundThread {
180             availableSubInfoList = SubscriptionUtil.getAvailableSubscriptions(context)
181             targetSubInfo =
182                 availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId }
183             targetSubInfo?.let { userSelectedSubInfoList.add(it) }
184             Log.d(TAG, "targetSubId: $targetSubId , targetSubInfo: $targetSubInfo")
185             slotInfoList = telephonyManager?.uiccSlotsInfo?.toList() ?: listOf()
186             Log.d(TAG, "slotInfoList: $slotInfoList.")
187             uiccCardInfoList = telephonyManager?.uiccCardsInfo!!
188             Log.d(TAG, "uiccCardInfoList: $uiccCardInfoList")
189 
190             targetPrimarySimCalls = SubscriptionManager.getDefaultVoiceSubscriptionId()
191             targetPrimarySimTexts = SubscriptionManager.getDefaultSmsSubscriptionId()
192             targetPrimarySimMobileData = SubscriptionManager.getDefaultDataSubscriptionId()
193 
194             Log.d(
195                 TAG,"doesTargetSimHaveEsimOperation: $doesTargetSimHaveEsimOperation" +
196                     ", isRemovableSimEnabled: $isRemovablePsimProfileEnabled" +
197                     ", isMultipleEnabledProfilesSupported: $isMultipleEnabledProfilesSupported" +
198                     ", targetPrimarySimCalls: $targetPrimarySimCalls" +
199                     ", targetPrimarySimTexts: $targetPrimarySimTexts" +
200                     ", targetPrimarySimMobileData: $targetPrimarySimMobileData")
201         }
202     }
203 
204     /**
205      * Return the subscriptionInfo list which has
206      * the target subscriptionInfo + active subscriptionInfo.
207      */
208     fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
209         var list: MutableList<SubscriptionInfo> = mutableListOf()
210         list.addAll(activeSubInfoList)
211         if (!list.contains(targetSubInfo)) {
212             targetSubInfo?.let { list.add(it) }
213         }
214 
215         return list.toList()
216     }
217 
218     /**
219      * Return the user selected SubscriptionInfo list.
220      */
221     fun getSelectedSubscriptionInfoList(): List<SubscriptionInfo> {
222         if (userSelectedSubInfoList.isEmpty()){
223             Log.d(TAG, "userSelectedSubInfoList is empty")
224             return activeSubInfoList
225         }
226         return userSelectedSubInfoList.toList()
227     }
228 
229     fun getSelectedSubscriptionInfoListWithRenaming(): List<SubscriptionInfo> {
230         if (userSelectedSubInfoList.isEmpty()){
231             Log.d(TAG, "userSelectedSubInfoList is empty")
232             return activeSubInfoList
233         }
234         return userSelectedSubInfoList.map {
235             SubscriptionInfo.Builder(it).setDisplayName(getSubscriptionInfoDisplayName(it)).build()
236         }.toList()
237     }
238 
239     fun addItemForRenaming(subInfo: SubscriptionInfo, newName: String) {
240         if (subInfo.displayName == newName) {
241             return
242         }
243         renameMutableMap[subInfo.subscriptionId] = newName
244         Log.d(
245             TAG,
246             "renameMutableMap add ${subInfo.subscriptionId} & $newName into: $renameMutableMap"
247         )
248     }
249 
250     fun getSubscriptionInfoDisplayName(subInfo: SubscriptionInfo): String {
251         return renameMutableMap[subInfo.subscriptionId] ?: subInfo.displayName.toString()
252     }
253 
254     fun addCurrentItemForSelectedSim() {
255         if (userSelectedSubInfoList.size < getActiveModemCount) {
256             userSelectedSubInfoList.addAll(
257                 activeSubInfoList.filter { !userSelectedSubInfoList.contains(it) }
258             )
259             Log.d(TAG,
260                 "addCurrentItemForSelectedSim: userSelectedSubInfoList: $userSelectedSubInfoList"
261             )
262         }
263     }
264 
265     fun addItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
266         if (!userSelectedSubInfoList.contains(selectedSubInfo)) {
267             userSelectedSubInfoList.add(selectedSubInfo)
268         }
269     }
270 
271     fun removeItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
272         if (userSelectedSubInfoList.contains(selectedSubInfo)) {
273             userSelectedSubInfoList.remove(selectedSubInfo)
274         }
275     }
276 
277     /**
278      * Return the subscriptionInfo which will be removed in the slot during the sim onboarding.
279      * If return Null, then no subscriptionInfo will be removed in the slot.
280      */
281     fun getRemovedSim():SubscriptionInfo?{
282         return activeSubInfoList.find { !userSelectedSubInfoList.contains(it) }
283     }
284 
285     fun handleTogglePsimAction() {
286         val canDisablePhysicalSubscription =
287             subscriptionManager?.canDisablePhysicalSubscription() == true
288         if (targetSubInfo != null && canDisablePhysicalSubscription) {
289             // TODO: to support disable case.
290             subscriptionManager?.setUiccApplicationsEnabled(
291                     targetSubInfo!!.subscriptionId, /*enabled=*/true)
292         } else {
293             Log.i(TAG, "The device does not support toggling pSIM. It is enough to just "
294                     + "enable the removable slot."
295             )
296         }
297     }
298 
299     fun isDsdsConditionSatisfied(): Boolean {
300         if (isMultiSimEnabled) {
301             Log.d(
302                 TAG,
303                 "DSDS is already enabled. Condition not satisfied."
304             )
305             return false
306         }
307         if (!isMultiSimSupported) {
308             Log.d(TAG, "Hardware does not support DSDS.")
309             return false
310         }
311         val isActiveSim = activeSubInfoList.isNotEmpty()
312         if (isMultipleEnabledProfilesSupported && isActiveSim) {
313             Log.d(TAG,
314                 "Device supports MEP and eSIM operation and eSIM profile is enabled."
315                         + " DSDS condition satisfied."
316             )
317             return true
318         }
319 
320         if (doesTargetSimHaveEsimOperation && isRemovablePsimProfileEnabled) {
321             Log.d(TAG,
322                 "eSIM operation and removable PSIM is enabled. DSDS condition satisfied."
323             )
324             return true
325         }
326 
327         if (!doesTargetSimHaveEsimOperation && isEsimProfileEnabled) {
328             Log.d(TAG,
329                 "Removable SIM operation and eSIM profile is enabled. DSDS condition"
330                         + " satisfied."
331             )
332             return true
333         }
334         Log.d(TAG, "DSDS condition not satisfied.")
335         return false
336     }
337 
338     fun startActivatingSim(){
339         // TODO: start to activate sim
340         callback(CallbackType.CALLBACK_FINISH)
341     }
342 
343     suspend fun startSetupName() {
344         withContext(Dispatchers.Default) {
345             renameMutableMap.forEach {
346                 subscriptionManager?.setDisplayName(
347                     it.value, it.key,
348                     SubscriptionManager.NAME_SOURCE_USER_INPUT
349                 )
350             }
351             // next action is SETUP_PRIMARY_SIM
352             callback(CallbackType.CALLBACK_SETUP_PRIMARY_SIM)
353         }
354     }
355 
356     suspend fun startSetupPrimarySim(
357         context: Context,
358         wifiPickerTrackerHelper: WifiPickerTrackerHelper
359     ) {
360         withContext(Dispatchers.Default) {
361                 setDefaultVoice(subscriptionManager, targetPrimarySimCalls)
362                 setDefaultSms(subscriptionManager, targetPrimarySimTexts)
363                 setDefaultData(
364                     context,
365                     subscriptionManager,
366                     wifiPickerTrackerHelper,
367                     targetPrimarySimMobileData
368                 )
369                 TelephonyRepository(context).setAutomaticData(
370                     targetNonDds,
371                     targetPrimarySimAutoDataSwitch.value
372                 )
373             }
374             // no next action, send finish
375             callback(CallbackType.CALLBACK_FINISH)
376     }
377 
378     suspend fun startEnableDsds(context: Context) {
379         withContext(Dispatchers.Default) {
380             Log.d(TAG, "User confirmed reboot to enable DSDS.")
381             SimActivationNotifier.setShowSimSettingsNotification(context, true)
382             telephonyManager?.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS)
383             callback(CallbackType.CALLBACK_FINISH)
384         }
385     }
386 
387     companion object{
388         private const val TAG = "SimOnboardingService"
389         const val NUM_OF_SIMS_FOR_DSDS = 2
390     }
391 }