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