1 /*
2  * Copyright (C) 2022 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.statusbar.pipeline.mobile.data.repository.prod
18 
19 import android.annotation.SuppressLint
20 import android.content.BroadcastReceiver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
25 import android.telephony.CellSignalStrengthCdma
26 import android.telephony.ServiceState
27 import android.telephony.SignalStrength
28 import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
29 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
30 import android.telephony.TelephonyCallback
31 import android.telephony.TelephonyDisplayInfo
32 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
33 import android.telephony.TelephonyManager
34 import android.telephony.TelephonyManager.ERI_FLASH
35 import android.telephony.TelephonyManager.ERI_ON
36 import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
37 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
38 import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
39 import com.android.settingslib.Utils
40 import com.android.systemui.broadcast.BroadcastDispatcher
41 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
42 import com.android.systemui.dagger.qualifiers.Application
43 import com.android.systemui.dagger.qualifiers.Background
44 import com.android.systemui.flags.FeatureFlags
45 import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
46 import com.android.systemui.log.table.TableLogBuffer
47 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
48 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
49 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
50 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
51 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
52 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
53 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
54 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
55 import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
56 import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
57 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
58 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
59 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
60 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
61 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
62 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
63 import javax.inject.Inject
64 import kotlinx.coroutines.CoroutineDispatcher
65 import kotlinx.coroutines.CoroutineScope
66 import kotlinx.coroutines.ExperimentalCoroutinesApi
67 import kotlinx.coroutines.asExecutor
68 import kotlinx.coroutines.channels.awaitClose
69 import kotlinx.coroutines.flow.Flow
70 import kotlinx.coroutines.flow.MutableStateFlow
71 import kotlinx.coroutines.flow.SharingStarted
72 import kotlinx.coroutines.flow.StateFlow
73 import kotlinx.coroutines.flow.asStateFlow
74 import kotlinx.coroutines.flow.callbackFlow
75 import kotlinx.coroutines.flow.filter
76 import kotlinx.coroutines.flow.map
77 import kotlinx.coroutines.flow.mapLatest
78 import kotlinx.coroutines.flow.mapNotNull
79 import kotlinx.coroutines.flow.onStart
80 import kotlinx.coroutines.flow.scan
81 import kotlinx.coroutines.flow.stateIn
82 
83 /**
84  * A repository implementation for a typical mobile connection (as opposed to a carrier merged
85  * connection -- see [CarrierMergedConnectionRepository]).
86  */
87 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
88 @OptIn(ExperimentalCoroutinesApi::class)
89 class MobileConnectionRepositoryImpl(
90     override val subId: Int,
91     private val context: Context,
92     subscriptionModel: StateFlow<SubscriptionModel?>,
93     defaultNetworkName: NetworkNameModel,
94     networkNameSeparator: String,
95     private val telephonyManager: TelephonyManager,
96     systemUiCarrierConfig: SystemUiCarrierConfig,
97     broadcastDispatcher: BroadcastDispatcher,
98     private val mobileMappingsProxy: MobileMappingsProxy,
99     bgDispatcher: CoroutineDispatcher,
100     logger: MobileInputLogger,
101     override val tableLogBuffer: TableLogBuffer,
102     flags: FeatureFlags,
103     scope: CoroutineScope,
104 ) : MobileConnectionRepository {
105     init {
106         if (telephonyManager.subscriptionId != subId) {
107             throw IllegalStateException(
108                 "MobileRepo: TelephonyManager should be created with subId($subId). " +
109                     "Found ${telephonyManager.subscriptionId} instead."
110             )
111         }
112     }
113 
114     /**
115      * This flow defines the single shared connection to system_server via TelephonyCallback. Any
116      * new callback should be added to this listener and funneled through callbackEvents via a data
117      * class. See [CallbackEvent] for defining new callbacks.
118      *
119      * The reason we need to do this is because TelephonyManager limits the number of registered
120      * listeners per-process, so we don't want to create a new listener for every callback.
121      *
122      * A note on the design for back pressure here: We don't control _which_ telephony callback
123      * comes in first, since we register every relevant bit of information as a batch. E.g., if a
124      * downstream starts collecting on a field which is backed by
125      * [TelephonyCallback.ServiceStateListener], it's not possible for us to guarantee that _that_
126      * callback comes in -- the first callback could very well be
127      * [TelephonyCallback.DataActivityListener], which would promptly be dropped if we didn't keep
128      * it tracked. We use the [scan] operator here to track the most recent callback of _each type_
129      * here. See [TelephonyCallbackState] to see how the callbacks are stored.
130      */
131     private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
132         val initial = TelephonyCallbackState()
133         callbackFlow {
134                 val callback =
135                     object :
136                         TelephonyCallback(),
137                         TelephonyCallback.ServiceStateListener,
138                         TelephonyCallback.SignalStrengthsListener,
139                         TelephonyCallback.DataConnectionStateListener,
140                         TelephonyCallback.DataActivityListener,
141                         TelephonyCallback.CarrierNetworkListener,
142                         TelephonyCallback.DisplayInfoListener,
143                         TelephonyCallback.DataEnabledListener {
144                         override fun onServiceStateChanged(serviceState: ServiceState) {
145                             logger.logOnServiceStateChanged(serviceState, subId)
146                             trySend(CallbackEvent.OnServiceStateChanged(serviceState))
147                         }
148 
149                         override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
150                             logger.logOnSignalStrengthsChanged(signalStrength, subId)
151                             trySend(CallbackEvent.OnSignalStrengthChanged(signalStrength))
152                         }
153 
154                         override fun onDataConnectionStateChanged(
155                             dataState: Int,
156                             networkType: Int
157                         ) {
158                             logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
159                             trySend(CallbackEvent.OnDataConnectionStateChanged(dataState))
160                         }
161 
162                         override fun onDataActivity(direction: Int) {
163                             logger.logOnDataActivity(direction, subId)
164                             trySend(CallbackEvent.OnDataActivity(direction))
165                         }
166 
167                         override fun onCarrierNetworkChange(active: Boolean) {
168                             logger.logOnCarrierNetworkChange(active, subId)
169                             trySend(CallbackEvent.OnCarrierNetworkChange(active))
170                         }
171 
172                         override fun onDisplayInfoChanged(
173                             telephonyDisplayInfo: TelephonyDisplayInfo
174                         ) {
175                             logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
176                             trySend(CallbackEvent.OnDisplayInfoChanged(telephonyDisplayInfo))
177                         }
178 
179                         override fun onDataEnabledChanged(enabled: Boolean, reason: Int) {
180                             logger.logOnDataEnabledChanged(enabled, subId)
181                             trySend(CallbackEvent.OnDataEnabledChanged(enabled))
182                         }
183                     }
184                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
185                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
186             }
187             .scan(initial = initial) { state, event -> state.applyEvent(event) }
188             .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initial)
189     }
190 
191     override val isEmergencyOnly =
192         callbackEvents
193             .mapNotNull { it.onServiceStateChanged }
194             .map { it.serviceState.isEmergencyOnly }
195             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
196 
197     override val isRoaming =
198         if (flags.isEnabled(ROAMING_INDICATOR_VIA_DISPLAY_INFO)) {
199                 callbackEvents
200                     .mapNotNull { it.onDisplayInfoChanged }
201                     .map { it.telephonyDisplayInfo.isRoaming }
202             } else {
203                 callbackEvents
204                     .mapNotNull { it.onServiceStateChanged }
205                     .map { it.serviceState.roaming }
206             }
207             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
208 
209     override val operatorAlphaShort =
210         callbackEvents
211             .mapNotNull { it.onServiceStateChanged }
212             .map { it.serviceState.operatorAlphaShort }
213             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
214 
215     override val isInService =
216         callbackEvents
217             .mapNotNull { it.onServiceStateChanged }
218             .map { Utils.isInService(it.serviceState) }
219             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
220 
221     override val isGsm =
222         callbackEvents
223             .mapNotNull { it.onSignalStrengthChanged }
224             .map { it.signalStrength.isGsm }
225             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
226 
227     override val cdmaLevel =
228         callbackEvents
229             .mapNotNull { it.onSignalStrengthChanged }
230             .map {
231                 it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
232                     strengths ->
233                     if (strengths.isNotEmpty()) {
234                         strengths[0].level
235                     } else {
236                         SIGNAL_STRENGTH_NONE_OR_UNKNOWN
237                     }
238                 }
239             }
240             .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
241 
242     override val primaryLevel =
243         callbackEvents
244             .mapNotNull { it.onSignalStrengthChanged }
245             .map { it.signalStrength.level }
246             .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
247 
248     override val dataConnectionState =
249         callbackEvents
250             .mapNotNull { it.onDataConnectionStateChanged }
251             .map { it.dataState.toDataConnectionType() }
252             .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
253 
254     override val dataActivityDirection =
255         callbackEvents
256             .mapNotNull { it.onDataActivity }
257             .map { it.direction.toMobileDataActivityModel() }
258             .stateIn(
259                 scope,
260                 SharingStarted.WhileSubscribed(),
261                 DataActivityModel(hasActivityIn = false, hasActivityOut = false)
262             )
263 
264     override val carrierNetworkChangeActive =
265         callbackEvents
266             .mapNotNull { it.onCarrierNetworkChange }
267             .map { it.active }
268             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
269 
270     override val resolvedNetworkType =
271         callbackEvents
272             .mapNotNull { it.onDisplayInfoChanged }
273             .map {
274                 if (it.telephonyDisplayInfo.overrideNetworkType != OVERRIDE_NETWORK_TYPE_NONE) {
275                     OverrideNetworkType(
276                         mobileMappingsProxy.toIconKeyOverride(
277                             it.telephonyDisplayInfo.overrideNetworkType
278                         )
279                     )
280                 } else if (it.telephonyDisplayInfo.networkType != NETWORK_TYPE_UNKNOWN) {
281                     DefaultNetworkType(
282                         mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
283                     )
284                 } else {
285                     UnknownNetworkType
286                 }
287             }
288             .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
289 
290     override val numberOfLevels =
291         systemUiCarrierConfig.shouldInflateSignalStrength
292             .map { shouldInflate ->
293                 if (shouldInflate) {
294                     DEFAULT_NUM_LEVELS + 1
295                 } else {
296                     DEFAULT_NUM_LEVELS
297                 }
298             }
299             .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
300 
301     override val carrierName =
302         subscriptionModel
303             .map {
304                 it?.let { model -> NetworkNameModel.SubscriptionDerived(model.carrierName) }
305                     ?: defaultNetworkName
306             }
307             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
308 
309     /**
310      * There are a few cases where we will need to poll [TelephonyManager] so we can update some
311      * internal state where callbacks aren't provided. Any of those events should be merged into
312      * this flow, which can be used to trigger the polling.
313      */
314     private val telephonyPollingEvent: Flow<Unit> = callbackEvents.map { Unit }
315 
316     override val cdmaRoaming: StateFlow<Boolean> =
317         telephonyPollingEvent
318             .mapLatest {
319                 val cdmaEri = telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
320                 cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
321             }
322             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
323 
324     override val carrierId =
325         broadcastDispatcher
326             .broadcastFlow(
327                 filter =
328                     IntentFilter(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED),
329                 map = { intent, _ -> intent },
330             )
331             .filter { intent ->
332                 intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
333             }
334             .map { it.carrierId() }
335             .onStart {
336                 // Make sure we get the initial carrierId
337                 emit(telephonyManager.simCarrierId)
338             }
339             .stateIn(scope, SharingStarted.WhileSubscribed(), telephonyManager.simCarrierId)
340 
341     /** BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here */
342     @SuppressLint("RegisterReceiverViaContext")
343     override val networkName: StateFlow<NetworkNameModel> =
344         conflatedCallbackFlow {
345                 val receiver =
346                     object : BroadcastReceiver() {
347                         override fun onReceive(context: Context, intent: Intent) {
348                             if (
349                                 intent.getIntExtra(
350                                     EXTRA_SUBSCRIPTION_INDEX,
351                                     INVALID_SUBSCRIPTION_ID
352                                 ) == subId
353                             ) {
354                                 logger.logServiceProvidersUpdatedBroadcast(intent)
355                                 trySend(
356                                     intent.toNetworkNameModel(networkNameSeparator)
357                                         ?: defaultNetworkName
358                                 )
359                             }
360                         }
361                     }
362 
363                 context.registerReceiver(
364                     receiver,
365                     IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)
366                 )
367 
368                 awaitClose { context.unregisterReceiver(receiver) }
369             }
370             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
371 
372     override val dataEnabled = run {
373         val initial = telephonyManager.isDataConnectionAllowed
374         callbackEvents
375             .mapNotNull { it.onDataEnabledChanged }
376             .map { it.enabled }
377             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
378     }
379 
380     /** Typical mobile connections aren't available during airplane mode. */
381     override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
382 
383     class Factory
384     @Inject
385     constructor(
386         private val context: Context,
387         private val broadcastDispatcher: BroadcastDispatcher,
388         private val telephonyManager: TelephonyManager,
389         private val logger: MobileInputLogger,
390         private val carrierConfigRepository: CarrierConfigRepository,
391         private val mobileMappingsProxy: MobileMappingsProxy,
392         private val flags: FeatureFlags,
393         @Background private val bgDispatcher: CoroutineDispatcher,
394         @Application private val scope: CoroutineScope,
395     ) {
396         fun build(
397             subId: Int,
398             mobileLogger: TableLogBuffer,
399             subscriptionModel: StateFlow<SubscriptionModel?>,
400             defaultNetworkName: NetworkNameModel,
401             networkNameSeparator: String,
402         ): MobileConnectionRepository {
403             return MobileConnectionRepositoryImpl(
404                 subId,
405                 context,
406                 subscriptionModel,
407                 defaultNetworkName,
408                 networkNameSeparator,
409                 telephonyManager.createForSubscriptionId(subId),
410                 carrierConfigRepository.getOrCreateConfigForSubId(subId),
411                 broadcastDispatcher,
412                 mobileMappingsProxy,
413                 bgDispatcher,
414                 logger,
415                 mobileLogger,
416                 flags,
417                 scope,
418             )
419         }
420     }
421 }
422 
423 private fun Intent.carrierId(): Int =
424     getIntExtra(TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID)
425 
426 /**
427  * Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single
428  * shared flow and then split them back out into other flows.
429  */
430 sealed interface CallbackEvent {
431     data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
432     data class OnDataActivity(val direction: Int) : CallbackEvent
433     data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
434     data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
435     data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
436     data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
437     data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
438 }
439 
440 /**
441  * A simple box type for 1-to-1 mapping of [CallbackEvent] to the batched event. Used in conjunction
442  * with [scan] to make sure we don't drop important callbacks due to late subscribers
443  */
444 data class TelephonyCallbackState(
445     val onDataActivity: CallbackEvent.OnDataActivity? = null,
446     val onCarrierNetworkChange: CallbackEvent.OnCarrierNetworkChange? = null,
447     val onDataConnectionStateChanged: CallbackEvent.OnDataConnectionStateChanged? = null,
448     val onDataEnabledChanged: CallbackEvent.OnDataEnabledChanged? = null,
449     val onDisplayInfoChanged: CallbackEvent.OnDisplayInfoChanged? = null,
450     val onServiceStateChanged: CallbackEvent.OnServiceStateChanged? = null,
451     val onSignalStrengthChanged: CallbackEvent.OnSignalStrengthChanged? = null,
452 ) {
453     fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
454         return when (event) {
455             is CallbackEvent.OnCarrierNetworkChange -> copy(onCarrierNetworkChange = event)
456             is CallbackEvent.OnDataActivity -> copy(onDataActivity = event)
457             is CallbackEvent.OnDataConnectionStateChanged ->
458                 copy(onDataConnectionStateChanged = event)
459             is CallbackEvent.OnDataEnabledChanged -> copy(onDataEnabledChanged = event)
460             is CallbackEvent.OnDisplayInfoChanged -> copy(onDisplayInfoChanged = event)
461             is CallbackEvent.OnServiceStateChanged -> {
462                 copy(onServiceStateChanged = event)
463             }
464             is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
465         }
466     }
467 }
468