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.systemui.bouncer.data.repository
18 
19 import android.annotation.SuppressLint
20 import android.content.IntentFilter
21 import android.content.res.Resources
22 import android.telephony.SubscriptionInfo
23 import android.telephony.SubscriptionManager
24 import android.telephony.TelephonyManager
25 import android.telephony.euicc.EuiccManager
26 import com.android.keyguard.KeyguardUpdateMonitor
27 import com.android.keyguard.KeyguardUpdateMonitorCallback
28 import com.android.systemui.bouncer.data.model.SimBouncerModel
29 import com.android.systemui.bouncer.data.model.SimPukInputModel
30 import com.android.systemui.broadcast.BroadcastDispatcher
31 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
32 import com.android.systemui.dagger.SysUISingleton
33 import com.android.systemui.dagger.qualifiers.Application
34 import com.android.systemui.dagger.qualifiers.Background
35 import com.android.systemui.dagger.qualifiers.Main
36 import com.android.systemui.res.R
37 import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
38 import javax.inject.Inject
39 import kotlinx.coroutines.CoroutineDispatcher
40 import kotlinx.coroutines.CoroutineScope
41 import kotlinx.coroutines.channels.awaitClose
42 import kotlinx.coroutines.flow.Flow
43 import kotlinx.coroutines.flow.MutableStateFlow
44 import kotlinx.coroutines.flow.SharingStarted
45 import kotlinx.coroutines.flow.StateFlow
46 import kotlinx.coroutines.flow.map
47 import kotlinx.coroutines.flow.merge
48 import kotlinx.coroutines.flow.stateIn
49 import kotlinx.coroutines.withContext
50 
51 /** Handles data layer logic for locked sim cards. */
52 interface SimBouncerRepository {
53     /** The subscription id of the current locked sim card. */
54     val subscriptionId: StateFlow<Int>
55     /** The active subscription of the current subscription id. */
56     val activeSubscriptionInfo: StateFlow<SubscriptionInfo?>
57     /**
58      * Determines if current sim card is an esim and is locked.
59      *
60      * A null value indicates that we do not know if we are esim locked or not.
61      */
62     val isLockedEsim: StateFlow<Boolean?>
63     /**
64      * Determines whether the current sim is locked requiring a PUK (Personal Unlocking Key) code.
65      */
66     val isSimPukLocked: StateFlow<Boolean>
67     /**
68      * The error message that should be displayed in an alert dialog.
69      *
70      * A null value indicates that the error dialog is not showing.
71      */
72     val errorDialogMessage: StateFlow<String?>
73     /** The state of the user flow on the SimPuk screen. */
74     val simPukInputModel: SimPukInputModel
75     /** Sets the state of the user flow on the SimPuk screen. */
76     fun setSimPukUserInput(enteredSimPuk: String? = null, enteredSimPin: String? = null)
77     /**
78      * Sets the error message when failing sim verification.
79      *
80      * A null value indicates that there is no error message to show.
81      */
82     fun setSimVerificationErrorMessage(msg: String?)
83 }
84 
85 @SysUISingleton
86 class SimBouncerRepositoryImpl
87 @Inject
88 constructor(
89     @Application private val applicationScope: CoroutineScope,
90     @Background private val backgroundDispatcher: CoroutineDispatcher,
91     @Main resources: Resources,
92     keyguardUpdateMonitor: KeyguardUpdateMonitor,
93     private val subscriptionManager: SubscriptionManagerProxy,
94     broadcastDispatcher: BroadcastDispatcher,
95     euiccManager: EuiccManager?,
96 ) : SimBouncerRepository {
97     private val isPukScreenAvailable: Boolean =
98         resources.getBoolean(com.android.internal.R.bool.config_enable_puk_unlock_screen)
99 
100     private val simBouncerModel: Flow<SimBouncerModel?> =
<lambda>null101         conflatedCallbackFlow {
102                 val callback =
103                     object : KeyguardUpdateMonitorCallback() {
104                         override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
105                             trySend(Unit)
106                         }
107                     }
108                 keyguardUpdateMonitor.registerCallback(callback)
109                 awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
110             }
<lambda>null111             .map {
112                 // Check to see if there is a locked sim puk card.
113                 val pukLockedSubId =
114                     withContext(backgroundDispatcher) {
115                         keyguardUpdateMonitor.getNextSubIdForState(
116                             TelephonyManager.SIM_STATE_PUK_REQUIRED
117                         )
118                     }
119                 if (
120                     isPukScreenAvailable &&
121                         subscriptionManager.isValidSubscriptionId(pukLockedSubId)
122                 ) {
123                     return@map (SimBouncerModel(isSimPukLocked = true, pukLockedSubId))
124                 }
125 
126                 // If there is no locked sim puk card, check to see if there is a locked sim card.
127                 val pinLockedSubId =
128                     withContext(backgroundDispatcher) {
129                         keyguardUpdateMonitor.getNextSubIdForState(
130                             TelephonyManager.SIM_STATE_PIN_REQUIRED
131                         )
132                     }
133                 if (subscriptionManager.isValidSubscriptionId(pinLockedSubId)) {
134                     return@map SimBouncerModel(isSimPukLocked = false, pinLockedSubId)
135                 }
136 
137                 return@map null // There is no locked sim.
138             }
139 
140     override val subscriptionId: StateFlow<Int> =
141         simBouncerModel
statenull142             .map { state -> state?.subscriptionId ?: INVALID_SUBSCRIPTION_ID }
143             .stateIn(
144                 scope = applicationScope,
145                 started = SharingStarted.WhileSubscribed(),
146                 initialValue = INVALID_SUBSCRIPTION_ID,
147             )
148 
149     @SuppressLint("MissingPermission")
150     override val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> =
151         subscriptionId
<lambda>null152             .map {
153                 withContext(backgroundDispatcher) {
154                     subscriptionManager.getActiveSubscriptionInfo(it)
155                 }
156             }
157             .stateIn(
158                 scope = applicationScope,
159                 started = SharingStarted.Eagerly,
160                 initialValue = null,
161             )
162 
163     @SuppressLint("MissingPermission")
164     override val isLockedEsim: StateFlow<Boolean?> =
165         activeSubscriptionInfo
infonull166             .map { info ->
167                 info?.let { euiccManager != null && euiccManager.isEnabled && info.isEmbedded }
168             }
169             .stateIn(
170                 scope = applicationScope,
171                 started = SharingStarted.Eagerly,
172                 initialValue = null,
173             )
174 
175     override val isSimPukLocked: StateFlow<Boolean> =
176         simBouncerModel
<lambda>null177             .map { it?.isSimPukLocked == true }
178             .stateIn(
179                 scope = applicationScope,
180                 started = SharingStarted.Eagerly,
181                 initialValue = false,
182             )
183 
184     private val disableEsimErrorMessage: Flow<String?> =
receivernull185         broadcastDispatcher.broadcastFlow(filter = IntentFilter(ACTION_DISABLE_ESIM)) { _, receiver
186             ->
187             if (receiver.resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
188                 resources.getString(R.string.error_disable_esim_msg)
189             } else {
190                 null
191             }
192         }
193 
194     private val simVerificationErrorMessage: MutableStateFlow<String?> = MutableStateFlow(null)
195 
196     override val errorDialogMessage: StateFlow<String?> =
197         merge(disableEsimErrorMessage, simVerificationErrorMessage)
198             .stateIn(
199                 scope = applicationScope,
200                 started = SharingStarted.WhileSubscribed(),
201                 initialValue = null,
202             )
203 
204     private var _simPukInputModel: SimPukInputModel = SimPukInputModel()
205     override val simPukInputModel: SimPukInputModel
206         get() = _simPukInputModel
207 
setSimPukUserInputnull208     override fun setSimPukUserInput(enteredSimPuk: String?, enteredSimPin: String?) {
209         _simPukInputModel = SimPukInputModel(enteredSimPuk, enteredSimPin)
210     }
211 
setSimVerificationErrorMessagenull212     override fun setSimVerificationErrorMessage(msg: String?) {
213         simVerificationErrorMessage.value = msg
214     }
215 
216     companion object {
217         const val ACTION_DISABLE_ESIM = "com.android.keyguard.disable_esim"
218         const val INVALID_SUBSCRIPTION_ID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
219     }
220 }
221