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.ui.viewmodel 18 19 import com.android.systemui.Flags.statusBarStaticInoutIndicators 20 import com.android.systemui.common.shared.model.ContentDescription 21 import com.android.systemui.common.shared.model.Icon 22 import com.android.systemui.log.table.logDiffsForTable 23 import com.android.systemui.res.R 24 import com.android.systemui.statusbar.core.NewStatusBarIcons 25 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor 26 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor 27 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor 28 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel 29 import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription 30 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants 31 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 32 import kotlinx.coroutines.CoroutineScope 33 import kotlinx.coroutines.flow.Flow 34 import kotlinx.coroutines.flow.MutableStateFlow 35 import kotlinx.coroutines.flow.SharingStarted 36 import kotlinx.coroutines.flow.StateFlow 37 import kotlinx.coroutines.flow.combine 38 import kotlinx.coroutines.flow.distinctUntilChanged 39 import kotlinx.coroutines.flow.flatMapLatest 40 import kotlinx.coroutines.flow.flowOf 41 import kotlinx.coroutines.flow.map 42 import kotlinx.coroutines.flow.mapLatest 43 import kotlinx.coroutines.flow.stateIn 44 45 /** Common interface for all of the location-based mobile icon view models. */ 46 interface MobileIconViewModelCommon { 47 val subscriptionId: Int 48 /** True if this view should be visible at all. */ 49 val isVisible: StateFlow<Boolean> 50 val icon: Flow<SignalIconModel> 51 val contentDescription: Flow<MobileContentDescription?> 52 val roaming: Flow<Boolean> 53 /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ 54 val networkTypeIcon: Flow<Icon.Resource?> 55 /** The slice attribution. Drawn as a background layer */ 56 val networkTypeBackground: StateFlow<Icon.Resource?> 57 val activityInVisible: Flow<Boolean> 58 val activityOutVisible: Flow<Boolean> 59 val activityContainerVisible: Flow<Boolean> 60 } 61 62 /** 63 * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over 64 * a single line of service via [MobileIconInteractor] and update the UI based on that 65 * subscription's information. 66 * 67 * There will be exactly one [MobileIconViewModel] per filtered subscription offered from 68 * [MobileIconsInteractor.filteredSubscriptions]. 69 * 70 * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon] 71 * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view 72 * model gets the exact same information, as well as allows us to log that unified state only once 73 * per icon. 74 */ 75 class MobileIconViewModel( 76 override val subscriptionId: Int, 77 iconInteractor: MobileIconInteractor, 78 airplaneModeInteractor: AirplaneModeInteractor, 79 constants: ConnectivityConstants, 80 scope: CoroutineScope, 81 ) : MobileIconViewModelCommon { <lambda>null82 private val cellProvider by lazy { 83 CellularIconViewModel( 84 subscriptionId, 85 iconInteractor, 86 airplaneModeInteractor, 87 constants, 88 scope, 89 ) 90 } 91 <lambda>null92 private val satelliteProvider by lazy { 93 CarrierBasedSatelliteViewModelImpl( 94 subscriptionId, 95 airplaneModeInteractor, 96 iconInteractor, 97 scope, 98 ) 99 } 100 101 /** 102 * Similar to repository switching, this allows us to split up the logic of satellite/cellular 103 * states, since they are different by nature 104 */ 105 private val vmProvider: Flow<MobileIconViewModelCommon> = 106 iconInteractor.isNonTerrestrial nonTerrestrialnull107 .mapLatest { nonTerrestrial -> 108 if (nonTerrestrial) { 109 satelliteProvider 110 } else { 111 cellProvider 112 } 113 } 114 .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider) 115 116 override val isVisible: StateFlow<Boolean> = 117 vmProvider <lambda>null118 .flatMapLatest { it.isVisible } 119 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 120 <lambda>null121 override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon } 122 123 override val contentDescription: Flow<MobileContentDescription?> = <lambda>null124 vmProvider.flatMapLatest { it.contentDescription } 125 <lambda>null126 override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming } 127 128 override val networkTypeIcon: Flow<Icon.Resource?> = <lambda>null129 vmProvider.flatMapLatest { it.networkTypeIcon } 130 131 override val networkTypeBackground: StateFlow<Icon.Resource?> = 132 vmProvider <lambda>null133 .flatMapLatest { it.networkTypeBackground } 134 .stateIn(scope, SharingStarted.WhileSubscribed(), null) 135 136 override val activityInVisible: Flow<Boolean> = <lambda>null137 vmProvider.flatMapLatest { it.activityInVisible } 138 139 override val activityOutVisible: Flow<Boolean> = <lambda>null140 vmProvider.flatMapLatest { it.activityOutVisible } 141 142 override val activityContainerVisible: Flow<Boolean> = <lambda>null143 vmProvider.flatMapLatest { it.activityContainerVisible } 144 } 145 146 /** Representation of this network when it is non-terrestrial (e.g., satellite) */ 147 private class CarrierBasedSatelliteViewModelImpl( 148 override val subscriptionId: Int, 149 airplaneModeInteractor: AirplaneModeInteractor, 150 interactor: MobileIconInteractor, 151 scope: CoroutineScope, 152 ) : MobileIconViewModelCommon { 153 override val isVisible: StateFlow<Boolean> = 154 airplaneModeInteractor.isAirplaneMode <lambda>null155 .map { !it } 156 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 157 158 override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon 159 160 override val contentDescription: Flow<MobileContentDescription?> = MutableStateFlow(null) 161 162 /** These fields are not used for satellite icons currently */ 163 override val roaming: Flow<Boolean> = flowOf(false) 164 override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null) 165 override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null) 166 override val activityInVisible: Flow<Boolean> = flowOf(false) 167 override val activityOutVisible: Flow<Boolean> = flowOf(false) 168 override val activityContainerVisible: Flow<Boolean> = flowOf(false) 169 } 170 171 /** Terrestrial (cellular) icon. */ 172 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 173 private class CellularIconViewModel( 174 override val subscriptionId: Int, 175 iconInteractor: MobileIconInteractor, 176 airplaneModeInteractor: AirplaneModeInteractor, 177 constants: ConnectivityConstants, 178 scope: CoroutineScope, 179 ) : MobileIconViewModelCommon { 180 override val isVisible: StateFlow<Boolean> = 181 if (!constants.hasDataCapabilities) { 182 flowOf(false) 183 } else { 184 combine( 185 airplaneModeInteractor.isAirplaneMode, 186 iconInteractor.isAllowedDuringAirplaneMode, 187 iconInteractor.isForceHidden, isAllowedDuringAirplaneModenull188 ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden -> 189 if (isForceHidden) { 190 false 191 } else if (isAirplaneMode) { 192 isAllowedDuringAirplaneMode 193 } else { 194 true 195 } 196 } 197 } 198 .distinctUntilChanged() 199 .logDiffsForTable( 200 iconInteractor.tableLogBuffer, 201 columnName = "visible", 202 initialValue = false, 203 ) 204 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 205 206 override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon 207 208 override val contentDescription: Flow<MobileContentDescription?> = nameModelnull209 combine(iconInteractor.signalLevelIcon, iconInteractor.networkName) { icon, nameModel -> 210 when (icon) { 211 is SignalIconModel.Cellular -> 212 MobileContentDescription.Cellular( 213 nameModel.name, 214 icon.levelDescriptionRes(), 215 ) 216 else -> null 217 } 218 } 219 .stateIn(scope, SharingStarted.WhileSubscribed(), null) 220 levelDescriptionResnull221 private fun SignalIconModel.Cellular.levelDescriptionRes() = 222 when (level) { 223 0 -> R.string.accessibility_no_signal 224 1 -> R.string.accessibility_one_bar 225 2 -> R.string.accessibility_two_bars 226 3 -> R.string.accessibility_three_bars 227 4 -> { 228 if (numberOfLevels == 6) { 229 R.string.accessibility_four_bars 230 } else { 231 R.string.accessibility_signal_full 232 } 233 } 234 5 -> { 235 if (numberOfLevels == 6) { 236 R.string.accessibility_signal_full 237 } else { 238 R.string.accessibility_no_signal 239 } 240 } 241 else -> R.string.accessibility_no_signal 242 } 243 244 private val showNetworkTypeIcon: Flow<Boolean> = 245 combine( 246 iconInteractor.isDataConnected, 247 iconInteractor.isDataEnabled, 248 iconInteractor.alwaysShowDataRatIcon, 249 iconInteractor.mobileIsDefault, 250 iconInteractor.carrierNetworkChangeActive, dataConnectednull251 ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault, carrierNetworkChange -> 252 alwaysShow || 253 (!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault)) 254 } 255 .distinctUntilChanged() 256 .logDiffsForTable( 257 iconInteractor.tableLogBuffer, 258 columnName = "showNetworkTypeIcon", 259 initialValue = false, 260 ) 261 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 262 263 override val networkTypeIcon: Flow<Icon.Resource?> = 264 combine(iconInteractor.networkTypeIconGroup, showNetworkTypeIcon) { networkTypeIconGroupnull265 networkTypeIconGroup, 266 shouldShow -> 267 val desc = 268 if (networkTypeIconGroup.contentDescription != 0) 269 ContentDescription.Resource(networkTypeIconGroup.contentDescription) 270 else null 271 val icon = 272 if (networkTypeIconGroup.iconId != 0) 273 Icon.Resource(networkTypeIconGroup.iconId, desc) 274 else null 275 return@combine when { 276 !shouldShow -> null 277 else -> icon 278 } 279 } 280 .distinctUntilChanged() 281 .stateIn(scope, SharingStarted.WhileSubscribed(), null) 282 283 override val networkTypeBackground = 284 iconInteractor.showSliceAttribution <lambda>null285 .map { 286 when { 287 it && NewStatusBarIcons.isEnabled -> 288 Icon.Resource(R.drawable.mobile_network_type_background_updated, null) 289 it -> Icon.Resource(R.drawable.mobile_network_type_background, null) 290 else -> null 291 } 292 } 293 .stateIn(scope, SharingStarted.WhileSubscribed(), null) 294 295 override val roaming: StateFlow<Boolean> = 296 iconInteractor.isRoaming 297 .logDiffsForTable( 298 iconInteractor.tableLogBuffer, 299 columnName = "roaming", 300 initialValue = false, 301 ) 302 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 303 304 private val activity: Flow<DataActivityModel?> = 305 if (!constants.shouldShowActivityConfig) { 306 flowOf(null) 307 } else { 308 iconInteractor.activity 309 } 310 311 override val activityInVisible: Flow<Boolean> = 312 activity <lambda>null313 .map { it?.hasActivityIn ?: false } 314 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 315 316 override val activityOutVisible: Flow<Boolean> = 317 activity <lambda>null318 .map { it?.hasActivityOut ?: false } 319 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 320 321 override val activityContainerVisible: Flow<Boolean> = 322 if (statusBarStaticInoutIndicators()) { 323 flowOf(constants.shouldShowActivityConfig) 324 } else { <lambda>null325 activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) } 326 } 327 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 328 } 329