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