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