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.telephony.CarrierConfigManager 20 import com.android.settingslib.SignalIcon.MobileIconGroup 21 import com.android.settingslib.mobile.TelephonyIcons.NOT_DEFAULT_DATA 22 import com.android.systemui.dagger.qualifiers.Application 23 import com.android.systemui.log.table.TableLogBuffer 24 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected 25 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel 26 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 27 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType 28 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository 29 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 30 import kotlinx.coroutines.CoroutineScope 31 import kotlinx.coroutines.ExperimentalCoroutinesApi 32 import kotlinx.coroutines.flow.Flow 33 import kotlinx.coroutines.flow.SharingStarted 34 import kotlinx.coroutines.flow.StateFlow 35 import kotlinx.coroutines.flow.combine 36 import kotlinx.coroutines.flow.distinctUntilChanged 37 import kotlinx.coroutines.flow.map 38 import kotlinx.coroutines.flow.mapLatest 39 import kotlinx.coroutines.flow.onEach 40 import kotlinx.coroutines.flow.stateIn 41 42 interface MobileIconInteractor { 43 /** The table log created for this connection */ 44 val tableLogBuffer: TableLogBuffer 45 46 /** The current mobile data activity */ 47 val activity: Flow<DataActivityModel> 48 49 /** 50 * This bit is meant to be `true` if and only if the default network capabilities (see 51 * [android.net.ConnectivityManager.registerDefaultNetworkCallback]) result in a network that 52 * has the [android.net.NetworkCapabilities.TRANSPORT_CELLULAR] represented. 53 * 54 * Note that this differs from [isDataConnected], which is tracked by telephony and has to do 55 * with the state of using this mobile connection for data as opposed to just voice. It is 56 * possible for a mobile subscription to be connected but not be in a connected data state, and 57 * thus we wouldn't want to show the network type icon. 58 */ 59 val isConnected: 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 /** Only true if mobile is the default transport but is not validated, otherwise false */ 70 val isDefaultConnectionFailed: StateFlow<Boolean> 71 72 /** True if we consider this connection to be in service, i.e. can make calls */ 73 val isInService: StateFlow<Boolean> 74 75 // TODO(b/256839546): clarify naming of default vs active 76 /** True if we want to consider the data connection enabled */ 77 val isDefaultDataEnabled: StateFlow<Boolean> 78 79 /** Observable for the data enabled state of this connection */ 80 val isDataEnabled: StateFlow<Boolean> 81 82 /** True if the RAT icon should always be displayed and false otherwise. */ 83 val alwaysShowDataRatIcon: StateFlow<Boolean> 84 85 /** True if the CDMA level should be preferred over the primary level. */ 86 val alwaysUseCdmaLevel: StateFlow<Boolean> 87 88 /** Observable for RAT type (network type) indicator */ 89 val networkTypeIconGroup: StateFlow<MobileIconGroup> 90 91 /** 92 * Provider name for this network connection. The name can be one of 3 values: 93 * 1. The default network name, if one is configured 94 * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED] 95 * 3. Or, in the case where the repository sends us the default network name, we check for an 96 * override in [connectionInfo.operatorAlphaShort], a value that is derived from 97 * [ServiceState] 98 */ 99 val networkName: StateFlow<NetworkNameModel> 100 101 /** True if this line of service is emergency-only */ 102 val isEmergencyOnly: StateFlow<Boolean> 103 104 /** 105 * True if this connection is considered roaming. The roaming bit can come from [ServiceState], 106 * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a 107 * connection to be roaming while carrier network change is active 108 */ 109 val isRoaming: StateFlow<Boolean> 110 111 /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */ 112 val level: StateFlow<Int> 113 114 /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */ 115 val numberOfLevels: StateFlow<Int> 116 117 /** See [MobileIconsInteractor.isForceHidden]. */ 118 val isForceHidden: Flow<Boolean> 119 } 120 121 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ 122 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 123 @OptIn(ExperimentalCoroutinesApi::class) 124 class MobileIconInteractorImpl( 125 @Application scope: CoroutineScope, 126 defaultSubscriptionHasDataEnabled: StateFlow<Boolean>, 127 override val alwaysShowDataRatIcon: StateFlow<Boolean>, 128 override val alwaysUseCdmaLevel: StateFlow<Boolean>, 129 defaultMobileConnectivity: StateFlow<MobileConnectivityModel>, 130 defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, 131 defaultMobileIconGroup: StateFlow<MobileIconGroup>, 132 defaultDataSubId: StateFlow<Int>, 133 override val isDefaultConnectionFailed: StateFlow<Boolean>, 134 override val isForceHidden: Flow<Boolean>, 135 connectionRepository: MobileConnectionRepository, 136 ) : MobileIconInteractor { 137 override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer 138 139 override val activity = connectionRepository.dataActivityDirection 140 <lambda>null141 override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected } 142 143 override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled 144 145 private val isDefault = 146 defaultDataSubId <lambda>null147 .mapLatest { connectionRepository.subId == it } 148 .stateIn( 149 scope, 150 SharingStarted.WhileSubscribed(), 151 connectionRepository.subId == defaultDataSubId.value 152 ) 153 154 override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled 155 156 override val networkName = 157 combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) { operatorAlphaShortnull158 operatorAlphaShort, 159 networkName -> 160 if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { 161 NetworkNameModel.IntentDerived(operatorAlphaShort) 162 } else { 163 networkName 164 } 165 } 166 .stateIn( 167 scope, 168 SharingStarted.WhileSubscribed(), 169 connectionRepository.networkName.value 170 ) 171 172 /** Observable for the current RAT indicator icon ([MobileIconGroup]) */ 173 override val networkTypeIconGroup: StateFlow<MobileIconGroup> = 174 combine( 175 connectionRepository.resolvedNetworkType, 176 defaultMobileIconMapping, 177 defaultMobileIconGroup, 178 isDefault, mappingnull179 ) { resolvedNetworkType, mapping, defaultGroup, isDefault -> 180 if (!isDefault) { 181 return@combine NOT_DEFAULT_DATA 182 } 183 184 when (resolvedNetworkType) { 185 is ResolvedNetworkType.CarrierMergedNetworkType -> 186 resolvedNetworkType.iconGroupOverride 187 else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup 188 } 189 } 190 .distinctUntilChanged() <lambda>null191 .onEach { 192 // Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the 193 // [Diffable] interface. 194 tableLogBuffer.logChange( 195 prefix = "", 196 columnName = "networkTypeIcon", 197 value = it.name 198 ) 199 } 200 .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) 201 202 override val isEmergencyOnly = connectionRepository.isEmergencyOnly 203 204 override val isRoaming: StateFlow<Boolean> = 205 combine( 206 connectionRepository.carrierNetworkChangeActive, 207 connectionRepository.isGsm, 208 connectionRepository.isRoaming, 209 connectionRepository.cdmaRoaming, isRoamingnull210 ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming -> 211 if (carrierNetworkChangeActive) { 212 false 213 } else if (isGsm) { 214 isRoaming 215 } else { 216 cdmaRoaming 217 } 218 } 219 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 220 221 override val level: StateFlow<Int> = 222 combine( 223 connectionRepository.isGsm, 224 connectionRepository.primaryLevel, 225 connectionRepository.cdmaLevel, 226 alwaysUseCdmaLevel, primaryLevelnull227 ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel -> 228 when { 229 // GSM connections should never use the CDMA level 230 isGsm -> primaryLevel 231 alwaysUseCdmaLevel -> cdmaLevel 232 else -> primaryLevel 233 } 234 } 235 .stateIn(scope, SharingStarted.WhileSubscribed(), 0) 236 237 override val numberOfLevels: StateFlow<Int> = 238 connectionRepository.numberOfLevels.stateIn( 239 scope, 240 SharingStarted.WhileSubscribed(), 241 connectionRepository.numberOfLevels.value, 242 ) 243 244 override val isDataConnected: StateFlow<Boolean> = 245 connectionRepository.dataConnectionState <lambda>null246 .map { it == Connected } 247 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 248 249 override val isInService = connectionRepository.isInService 250 } 251