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.domain.interactor 18 19 import android.content.Context 20 import com.android.internal.telephony.flags.Flags 21 import com.android.settingslib.SignalIcon.MobileIconGroup 22 import com.android.settingslib.graph.SignalDrawable 23 import com.android.settingslib.mobile.MobileIconCarrierIdOverrides 24 import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl 25 import com.android.systemui.dagger.qualifiers.Application 26 import com.android.systemui.log.table.TableLogBuffer 27 import com.android.systemui.log.table.logDiffsForTable 28 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected 29 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 30 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType 31 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository 32 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel 33 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon 34 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon 35 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel 36 import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel 37 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 38 import kotlinx.coroutines.CoroutineScope 39 import kotlinx.coroutines.ExperimentalCoroutinesApi 40 import kotlinx.coroutines.flow.Flow 41 import kotlinx.coroutines.flow.MutableStateFlow 42 import kotlinx.coroutines.flow.SharingStarted 43 import kotlinx.coroutines.flow.StateFlow 44 import kotlinx.coroutines.flow.asStateFlow 45 import kotlinx.coroutines.flow.combine 46 import kotlinx.coroutines.flow.distinctUntilChanged 47 import kotlinx.coroutines.flow.flatMapLatest 48 import kotlinx.coroutines.flow.map 49 import kotlinx.coroutines.flow.stateIn 50 51 interface MobileIconInteractor { 52 /** The table log created for this connection */ 53 val tableLogBuffer: TableLogBuffer 54 55 /** The current mobile data activity */ 56 val activity: Flow<DataActivityModel> 57 58 /** See [MobileConnectionsRepository.mobileIsDefault]. */ 59 val mobileIsDefault: Flow<Boolean> 60 61 /** 62 * True when telephony tells us that the data state is CONNECTED. See 63 * [android.telephony.TelephonyCallback.DataConnectionStateListener] for more details. We 64 * consider this connection to be serving data, and thus want to show a network type icon, when 65 * data is connected. Other data connection states would typically cause us not to show the icon 66 */ 67 val isDataConnected: StateFlow<Boolean> 68 69 /** True if we consider this connection to be in service, i.e. can make calls */ 70 val isInService: StateFlow<Boolean> 71 72 /** True if this connection is emergency only */ 73 val isEmergencyOnly: StateFlow<Boolean> 74 75 /** Observable for the data enabled state of this connection */ 76 val isDataEnabled: StateFlow<Boolean> 77 78 /** True if the RAT icon should always be displayed and false otherwise. */ 79 val alwaysShowDataRatIcon: StateFlow<Boolean> 80 81 /** Canonical representation of the current mobile signal strength as a triangle. */ 82 val signalLevelIcon: StateFlow<SignalIconModel> 83 84 /** Observable for RAT type (network type) indicator */ 85 val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> 86 87 /** Whether or not to show the slice attribution */ 88 val showSliceAttribution: StateFlow<Boolean> 89 90 /** True if this connection is satellite-based */ 91 val isNonTerrestrial: StateFlow<Boolean> 92 93 /** 94 * Provider name for this network connection. The name can be one of 3 values: 95 * 1. The default network name, if one is configured 96 * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED] 97 * 3. Or, in the case where the repository sends us the default network name, we check for an 98 * override in [connectionInfo.operatorAlphaShort], a value that is derived from 99 * [ServiceState] 100 */ 101 val networkName: StateFlow<NetworkNameModel> 102 103 /** 104 * Provider name for this network connection. The name can be one of 3 values: 105 * 1. The default network name, if one is configured 106 * 2. A name provided by the [SubscriptionModel] of this network connection 107 * 3. Or, in the case where the repository sends us the default network name, we check for an 108 * override in [connectionInfo.operatorAlphaShort], a value that is derived from 109 * [ServiceState] 110 * 111 * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data 112 * provided is identical 113 */ 114 val carrierName: StateFlow<String> 115 116 /** True if there is only one active subscription. */ 117 val isSingleCarrier: StateFlow<Boolean> 118 119 /** 120 * True if this connection is considered roaming. The roaming bit can come from [ServiceState], 121 * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a 122 * connection to be roaming while carrier network change is active 123 */ 124 val isRoaming: StateFlow<Boolean> 125 126 /** See [MobileIconsInteractor.isForceHidden]. */ 127 val isForceHidden: Flow<Boolean> 128 129 /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */ 130 val isAllowedDuringAirplaneMode: StateFlow<Boolean> 131 132 /** True when in carrier network change mode */ 133 val carrierNetworkChangeActive: StateFlow<Boolean> 134 } 135 136 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ 137 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 138 @OptIn(ExperimentalCoroutinesApi::class) 139 class MobileIconInteractorImpl( 140 @Application scope: CoroutineScope, 141 defaultSubscriptionHasDataEnabled: StateFlow<Boolean>, 142 override val alwaysShowDataRatIcon: StateFlow<Boolean>, 143 alwaysUseCdmaLevel: StateFlow<Boolean>, 144 override val isSingleCarrier: StateFlow<Boolean>, 145 override val mobileIsDefault: StateFlow<Boolean>, 146 defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, 147 defaultMobileIconGroup: StateFlow<MobileIconGroup>, 148 isDefaultConnectionFailed: StateFlow<Boolean>, 149 override val isForceHidden: Flow<Boolean>, 150 connectionRepository: MobileConnectionRepository, 151 private val context: Context, 152 val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() 153 ) : MobileIconInteractor { 154 override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer 155 156 override val activity = connectionRepository.dataActivityDirection 157 158 override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled 159 160 override val carrierNetworkChangeActive: StateFlow<Boolean> = 161 connectionRepository.carrierNetworkChangeActive 162 163 // True if there exists _any_ icon override for this carrierId. Note that overrides can include 164 // any or none of the icon groups defined in MobileMappings, so we still need to check on a 165 // per-network-type basis whether or not the given icon group is overridden 166 private val carrierIdIconOverrideExists = 167 connectionRepository.carrierId <lambda>null168 .map { carrierIdOverrides.carrierIdEntryExists(it) } 169 .distinctUntilChanged() 170 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 171 172 override val networkName = 173 combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) { operatorAlphaShortnull174 operatorAlphaShort, 175 networkName -> 176 if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { 177 NetworkNameModel.IntentDerived(operatorAlphaShort) 178 } else { 179 networkName 180 } 181 } 182 .stateIn( 183 scope, 184 SharingStarted.WhileSubscribed(), 185 connectionRepository.networkName.value 186 ) 187 188 override val carrierName = 189 combine(connectionRepository.operatorAlphaShort, connectionRepository.carrierName) { operatorAlphaShortnull190 operatorAlphaShort, 191 networkName -> 192 if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { 193 operatorAlphaShort 194 } else { 195 networkName.name 196 } 197 } 198 .stateIn( 199 scope, 200 SharingStarted.WhileSubscribed(), 201 connectionRepository.carrierName.value.name 202 ) 203 204 /** What the mobile icon would be before carrierId overrides */ 205 private val defaultNetworkType: StateFlow<MobileIconGroup> = 206 combine( 207 connectionRepository.resolvedNetworkType, 208 defaultMobileIconMapping, 209 defaultMobileIconGroup, mappingnull210 ) { resolvedNetworkType, mapping, defaultGroup -> 211 when (resolvedNetworkType) { 212 is ResolvedNetworkType.CarrierMergedNetworkType -> 213 resolvedNetworkType.iconGroupOverride 214 else -> { 215 mapping[resolvedNetworkType.lookupKey] ?: defaultGroup 216 } 217 } 218 } 219 .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) 220 221 override val networkTypeIconGroup = 222 combine( 223 defaultNetworkType, 224 carrierIdIconOverrideExists, overrideExistsnull225 ) { networkType, overrideExists -> 226 // DefaultIcon comes out of the icongroup lookup, we check for overrides here 227 if (overrideExists) { 228 val iconOverride = 229 carrierIdOverrides.getOverrideFor( 230 connectionRepository.carrierId.value, 231 networkType.name, 232 context.resources, 233 ) 234 if (iconOverride > 0) { 235 OverriddenIcon(networkType, iconOverride) 236 } else { 237 DefaultIcon(networkType) 238 } 239 } else { 240 DefaultIcon(networkType) 241 } 242 } 243 .distinctUntilChanged() 244 .logDiffsForTable( 245 tableLogBuffer = tableLogBuffer, 246 columnPrefix = "", 247 initialValue = DefaultIcon(defaultMobileIconGroup.value), 248 ) 249 .stateIn( 250 scope, 251 SharingStarted.WhileSubscribed(), 252 DefaultIcon(defaultMobileIconGroup.value), 253 ) 254 255 override val showSliceAttribution: StateFlow<Boolean> = 256 combine( 257 connectionRepository.allowNetworkSliceIndicator, 258 connectionRepository.hasPrioritizedNetworkCapabilities, allowednull259 ) { allowed, hasPrioritizedNetworkCapabilities -> 260 allowed && hasPrioritizedNetworkCapabilities 261 } 262 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 263 264 override val isNonTerrestrial: StateFlow<Boolean> = 265 if (Flags.carrierEnabledSatelliteFlag()) { 266 connectionRepository.isNonTerrestrial 267 } else { 268 MutableStateFlow(false).asStateFlow() 269 } 270 271 override val isRoaming: StateFlow<Boolean> = 272 combine( 273 connectionRepository.carrierNetworkChangeActive, 274 connectionRepository.isGsm, 275 connectionRepository.isRoaming, 276 connectionRepository.cdmaRoaming, isRoamingnull277 ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming -> 278 if (carrierNetworkChangeActive) { 279 false 280 } else if (isGsm) { 281 isRoaming 282 } else { 283 cdmaRoaming 284 } 285 } 286 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 287 288 private val level: StateFlow<Int> = 289 combine( 290 connectionRepository.isGsm, 291 connectionRepository.primaryLevel, 292 connectionRepository.cdmaLevel, 293 alwaysUseCdmaLevel, primaryLevelnull294 ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel -> 295 when { 296 // GSM connections should never use the CDMA level 297 isGsm -> primaryLevel 298 alwaysUseCdmaLevel -> cdmaLevel 299 else -> primaryLevel 300 } 301 } 302 .stateIn(scope, SharingStarted.WhileSubscribed(), 0) 303 304 private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels 305 306 override val isDataConnected: StateFlow<Boolean> = 307 connectionRepository.dataConnectionState <lambda>null308 .map { it == Connected } 309 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 310 311 override val isInService = connectionRepository.isInService 312 313 override val isEmergencyOnly: StateFlow<Boolean> = connectionRepository.isEmergencyOnly 314 315 override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode 316 317 /** Whether or not to show the error state of [SignalDrawable] */ 318 private val showExclamationMark: StateFlow<Boolean> = 319 combine( 320 defaultSubscriptionHasDataEnabled, 321 isDefaultConnectionFailed, 322 isInService, isDefaultDataEnablednull323 ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService -> 324 !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService 325 } 326 .stateIn(scope, SharingStarted.WhileSubscribed(), true) 327 328 private val shownLevel: StateFlow<Int> = 329 combine( 330 level, 331 isInService, 332 connectionRepository.inflateSignalStrength, inflatenull333 ) { level, isInService, inflate -> 334 if (isInService) { 335 if (inflate) level + 1 else level 336 } else 0 337 } 338 .stateIn(scope, SharingStarted.WhileSubscribed(), 0) 339 340 private val cellularIcon: Flow<SignalIconModel.Cellular> = 341 combine( 342 shownLevel, 343 numberOfLevels, 344 showExclamationMark, 345 carrierNetworkChangeActive, shownLevelnull346 ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange -> 347 SignalIconModel.Cellular( 348 shownLevel, 349 numberOfLevels, 350 showExclamationMark, 351 carrierNetworkChange, 352 ) 353 } 354 355 private val satelliteIcon: Flow<SignalIconModel.Satellite> = <lambda>null356 shownLevel.map { 357 SignalIconModel.Satellite( 358 level = it, 359 icon = 360 SatelliteIconModel.fromSignalStrength(it) 361 ?: SatelliteIconModel.fromSignalStrength(0)!! 362 ) 363 } 364 <lambda>null365 override val signalLevelIcon: StateFlow<SignalIconModel> = run { 366 val initial = 367 SignalIconModel.Cellular( 368 shownLevel.value, 369 numberOfLevels.value, 370 showExclamationMark.value, 371 carrierNetworkChangeActive.value, 372 ) 373 isNonTerrestrial 374 .flatMapLatest { ntn -> 375 if (ntn) { 376 satelliteIcon 377 } else { 378 cellularIcon 379 } 380 } 381 .distinctUntilChanged() 382 .logDiffsForTable( 383 tableLogBuffer, 384 columnPrefix = "icon", 385 initialValue = initial, 386 ) 387 .stateIn(scope, SharingStarted.WhileSubscribed(), initial) 388 } 389 } 390