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.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH 20 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE 21 import com.android.settingslib.graph.SignalDrawable 22 import com.android.systemui.common.shared.model.ContentDescription 23 import com.android.systemui.common.shared.model.Icon 24 import com.android.systemui.log.table.logDiffsForTable 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.ui.model.SignalIconModel 29 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants 30 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 31 import kotlinx.coroutines.CoroutineScope 32 import kotlinx.coroutines.ExperimentalCoroutinesApi 33 import kotlinx.coroutines.flow.Flow 34 import kotlinx.coroutines.flow.SharingStarted 35 import kotlinx.coroutines.flow.StateFlow 36 import kotlinx.coroutines.flow.combine 37 import kotlinx.coroutines.flow.distinctUntilChanged 38 import kotlinx.coroutines.flow.flowOf 39 import kotlinx.coroutines.flow.map 40 import kotlinx.coroutines.flow.mapLatest 41 import kotlinx.coroutines.flow.stateIn 42 43 /** Common interface for all of the location-based mobile icon view models. */ 44 interface MobileIconViewModelCommon { 45 val subscriptionId: Int 46 /** True if this view should be visible at all. */ 47 val isVisible: StateFlow<Boolean> 48 val icon: Flow<SignalIconModel> 49 val contentDescription: Flow<ContentDescription> 50 val roaming: Flow<Boolean> 51 /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ 52 val networkTypeIcon: Flow<Icon.Resource?> 53 val activityInVisible: Flow<Boolean> 54 val activityOutVisible: Flow<Boolean> 55 val activityContainerVisible: Flow<Boolean> 56 } 57 58 /** 59 * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over 60 * a single line of service via [MobileIconInteractor] and update the UI based on that 61 * subscription's information. 62 * 63 * There will be exactly one [MobileIconViewModel] per filtered subscription offered from 64 * [MobileIconsInteractor.filteredSubscriptions]. 65 * 66 * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon] 67 * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view 68 * model gets the exact same information, as well as allows us to log that unified state only once 69 * per icon. 70 */ 71 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 72 @OptIn(ExperimentalCoroutinesApi::class) 73 class MobileIconViewModel 74 constructor( 75 override val subscriptionId: Int, 76 iconInteractor: MobileIconInteractor, 77 airplaneModeInteractor: AirplaneModeInteractor, 78 constants: ConnectivityConstants, 79 scope: CoroutineScope, 80 ) : MobileIconViewModelCommon { 81 /** Whether or not to show the error state of [SignalDrawable] */ 82 private val showExclamationMark: Flow<Boolean> = <lambda>null83 iconInteractor.isDefaultDataEnabled.mapLatest { !it } 84 85 override val isVisible: StateFlow<Boolean> = 86 if (!constants.hasDataCapabilities) { 87 flowOf(false) 88 } else { 89 combine( 90 airplaneModeInteractor.isAirplaneMode, 91 iconInteractor.isForceHidden, isForceHiddennull92 ) { isAirplaneMode, isForceHidden -> 93 !isAirplaneMode && !isForceHidden 94 } 95 } 96 .distinctUntilChanged() 97 .logDiffsForTable( 98 iconInteractor.tableLogBuffer, 99 columnPrefix = "", 100 columnName = "visible", 101 initialValue = false, 102 ) 103 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 104 <lambda>null105 override val icon: Flow<SignalIconModel> = run { 106 val initial = SignalIconModel.createEmptyState(iconInteractor.numberOfLevels.value) 107 combine( 108 iconInteractor.level, 109 iconInteractor.numberOfLevels, 110 showExclamationMark, 111 iconInteractor.isInService, 112 ) { level, numberOfLevels, showExclamationMark, isInService -> 113 if (!isInService) { 114 SignalIconModel.createEmptyState(numberOfLevels) 115 } else { 116 SignalIconModel(level, numberOfLevels, showExclamationMark) 117 } 118 } 119 .distinctUntilChanged() 120 .logDiffsForTable( 121 iconInteractor.tableLogBuffer, 122 columnPrefix = "icon", 123 initialValue = initial, 124 ) 125 .stateIn(scope, SharingStarted.WhileSubscribed(), initial) 126 } 127 <lambda>null128 override val contentDescription: Flow<ContentDescription> = run { 129 val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH_NONE) 130 combine( 131 iconInteractor.level, 132 iconInteractor.isInService, 133 ) { level, isInService -> 134 val resId = 135 when { 136 isInService -> PHONE_SIGNAL_STRENGTH[level] 137 else -> PHONE_SIGNAL_STRENGTH_NONE 138 } 139 ContentDescription.Resource(resId) 140 } 141 .distinctUntilChanged() 142 .stateIn(scope, SharingStarted.WhileSubscribed(), initial) 143 } 144 145 private val showNetworkTypeIcon: Flow<Boolean> = 146 combine( 147 iconInteractor.isDataConnected, 148 iconInteractor.isDataEnabled, 149 iconInteractor.isDefaultConnectionFailed, 150 iconInteractor.alwaysShowDataRatIcon, 151 iconInteractor.isConnected, dataConnectednull152 ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected -> 153 alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected) 154 } 155 .distinctUntilChanged() 156 .logDiffsForTable( 157 iconInteractor.tableLogBuffer, 158 columnPrefix = "", 159 columnName = "showNetworkTypeIcon", 160 initialValue = false, 161 ) 162 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 163 164 override val networkTypeIcon: Flow<Icon.Resource?> = 165 combine( 166 iconInteractor.networkTypeIconGroup, 167 showNetworkTypeIcon, networkTypeIconGroupnull168 ) { networkTypeIconGroup, shouldShow -> 169 val desc = 170 if (networkTypeIconGroup.dataContentDescription != 0) 171 ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) 172 else null 173 val icon = Icon.Resource(networkTypeIconGroup.dataType, desc) 174 return@combine when { 175 !shouldShow -> null 176 else -> icon 177 } 178 } 179 .distinctUntilChanged() 180 .stateIn(scope, SharingStarted.WhileSubscribed(), null) 181 182 override val roaming: StateFlow<Boolean> = 183 iconInteractor.isRoaming 184 .logDiffsForTable( 185 iconInteractor.tableLogBuffer, 186 columnPrefix = "", 187 columnName = "roaming", 188 initialValue = false, 189 ) 190 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 191 192 private val activity: Flow<DataActivityModel?> = 193 if (!constants.shouldShowActivityConfig) { 194 flowOf(null) 195 } else { 196 iconInteractor.activity 197 } 198 199 override val activityInVisible: Flow<Boolean> = 200 activity <lambda>null201 .map { it?.hasActivityIn ?: false } 202 .distinctUntilChanged() 203 .logDiffsForTable( 204 iconInteractor.tableLogBuffer, 205 columnPrefix = "", 206 columnName = "activityInVisible", 207 initialValue = false, 208 ) 209 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 210 211 override val activityOutVisible: Flow<Boolean> = 212 activity <lambda>null213 .map { it?.hasActivityOut ?: false } 214 .distinctUntilChanged() 215 .logDiffsForTable( 216 iconInteractor.tableLogBuffer, 217 columnPrefix = "", 218 columnName = "activityOutVisible", 219 initialValue = false, 220 ) 221 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 222 223 override val activityContainerVisible: Flow<Boolean> = 224 activity <lambda>null225 .map { it != null && (it.hasActivityIn || it.hasActivityOut) } 226 .distinctUntilChanged() 227 .logDiffsForTable( 228 iconInteractor.tableLogBuffer, 229 columnPrefix = "", 230 columnName = "activityContainerVisible", 231 initialValue = false, 232 ) 233 .stateIn(scope, SharingStarted.WhileSubscribed(), false) 234 } 235