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