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