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