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.telephony
18
19 import android.content.Context
20 import android.telephony.SubscriptionInfo
21 import android.telephony.SubscriptionManager
22 import android.util.Log
23 import androidx.lifecycle.LifecycleOwner
24 import com.android.settings.network.SubscriptionUtil
25 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
26 import kotlinx.coroutines.Dispatchers
27 import kotlinx.coroutines.asExecutor
28 import kotlinx.coroutines.channels.awaitClose
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.callbackFlow
31 import kotlinx.coroutines.flow.conflate
32 import kotlinx.coroutines.flow.filterNot
33 import kotlinx.coroutines.flow.flowOf
34 import kotlinx.coroutines.flow.flowOn
35 import kotlinx.coroutines.flow.map
36 import kotlinx.coroutines.flow.onEach
37
38 private const val TAG = "SubscriptionRepository"
39
40 class SubscriptionRepository(private val context: Context) {
41 private val subscriptionManager = context.requireSubscriptionManager()
42
43 /**
44 * Return a list of subscriptions that are available and visible to the user.
45 *
46 * @return list of user selectable subscriptions.
47 */
48 fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> =
49 context.getSelectableSubscriptionInfoList()
50
51 /** Flow of whether the subscription enabled for the given [subId]. */
52 fun isSubscriptionEnabledFlow(subId: Int): Flow<Boolean> {
53 if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
54 return context.subscriptionsChangedFlow()
55 .map { subscriptionManager.isSubscriptionEnabled(subId) }
56 .conflate()
57 .onEach { Log.d(TAG, "[$subId] isSubscriptionEnabledFlow: $it") }
58 .flowOn(Dispatchers.Default)
59 }
60
61 /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
62 fun collectSubscriptionEnabled(
63 subId: Int,
64 lifecycleOwner: LifecycleOwner,
65 action: (Boolean) -> Unit,
66 ) {
67 isSubscriptionEnabledFlow(subId).collectLatestWithLifecycle(lifecycleOwner, action = action)
68 }
69
70 fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription()
71 }
72
73 val Context.subscriptionManager: SubscriptionManager?
74 get() = getSystemService(SubscriptionManager::class.java)
75
Contextnull76 fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!!
77
78 fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
79 SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
80 }.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
81
Contextnull82 fun Context.subscriptionsChangedFlow() = callbackFlow {
83 val subscriptionManager = requireSubscriptionManager()
84
85 val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
86 override fun onSubscriptionsChanged() {
87 trySend(Unit)
88 }
89 }
90
91 subscriptionManager.addOnSubscriptionsChangedListener(
92 Dispatchers.Default.asExecutor(),
93 listener,
94 )
95
96 awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
97 }.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
98
99 /**
100 * Return a list of subscriptions that are available and visible to the user.
101 *
102 * @return list of user selectable subscriptions.
103 */
Contextnull104 fun Context.getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
105 val subscriptionManager = requireSubscriptionManager()
106 val availableList = subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
107 val visibleList = availableList.filter { subInfo ->
108 // Opportunistic subscriptions are considered invisible
109 // to users so they should never be returned.
110 SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo)
111 }
112 return visibleList
113 .groupBy { it.groupUuid }
114 .flatMap { (groupUuid, subInfos) ->
115 if (groupUuid == null) {
116 subInfos
117 } else {
118 // Multiple subscriptions in a group should only have one representative.
119 // It should be the current active primary subscription if any, or the primary
120 // subscription with minimum subscription id.
121 subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
122 .ifEmpty { subInfos.sortedBy { it.subscriptionId } }
123 .take(1)
124 }
125 }
126 // Matching the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList
127 .sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId }))
128 .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
129 }
130
131 /** Subscription with invalid sim slot index has lowest sort order. */
132 private val SubscriptionInfo.sortableSimSlotIndex: Int
133 get() = if (simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
134 simSlotIndex
135 } else {
136 Int.MAX_VALUE
137 }
138