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.wifi.ui.viewmodel 18 19 import android.content.Context 20 import androidx.annotation.StringRes 21 import androidx.annotation.VisibleForTesting 22 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH 23 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION 24 import com.android.systemui.R 25 import com.android.systemui.common.shared.model.ContentDescription 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Application 28 import com.android.systemui.log.table.TableLogBuffer 29 import com.android.systemui.log.table.logDiffsForTable 30 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS 31 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS 32 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK 33 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags 34 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel 35 import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog 36 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants 37 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 38 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor 39 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants 40 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel 41 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon 42 import javax.inject.Inject 43 import kotlinx.coroutines.CoroutineScope 44 import kotlinx.coroutines.flow.Flow 45 import kotlinx.coroutines.flow.SharingStarted 46 import kotlinx.coroutines.flow.StateFlow 47 import kotlinx.coroutines.flow.combine 48 import kotlinx.coroutines.flow.distinctUntilChanged 49 import kotlinx.coroutines.flow.flowOf 50 import kotlinx.coroutines.flow.map 51 import kotlinx.coroutines.flow.stateIn 52 53 /** 54 * Models the UI state for the status bar wifi icon. 55 * 56 * This class exposes three view models, one per status bar location: [home], [keyguard], and [qs]. 57 * In order to get the UI state for the wifi icon, you must use one of those view models (whichever 58 * is correct for your location). 59 * 60 * Internally, this class maintains the current state of the wifi icon and notifies those three view 61 * models of any changes. 62 */ 63 @SysUISingleton 64 class WifiViewModel 65 @Inject 66 constructor( 67 airplaneModeViewModel: AirplaneModeViewModel, 68 connectivityConstants: ConnectivityConstants, 69 private val context: Context, 70 @WifiTableLog wifiTableLogBuffer: TableLogBuffer, 71 interactor: WifiInteractor, 72 @Application private val scope: CoroutineScope, 73 statusBarPipelineFlags: StatusBarPipelineFlags, 74 wifiConstants: WifiConstants, 75 ) { 76 /** Returns the icon to use based on the given network. */ 77 private fun WifiNetworkModel.icon(): WifiIcon { 78 return when (this) { 79 is WifiNetworkModel.Unavailable -> WifiIcon.Hidden 80 is WifiNetworkModel.Invalid -> WifiIcon.Hidden 81 is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden 82 is WifiNetworkModel.Inactive -> 83 WifiIcon.Visible( 84 res = WIFI_NO_NETWORK, 85 ContentDescription.Loaded( 86 "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}" 87 ) 88 ) 89 is WifiNetworkModel.Active -> { 90 val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level]) 91 when { 92 this.isValidated -> 93 WifiIcon.Visible( 94 WIFI_FULL_ICONS[this.level], 95 ContentDescription.Loaded(levelDesc), 96 ) 97 else -> 98 WifiIcon.Visible( 99 WIFI_NO_INTERNET_ICONS[this.level], 100 ContentDescription.Loaded( 101 "$levelDesc,${context.getString(NO_INTERNET)}" 102 ), 103 ) 104 } 105 } 106 } 107 } 108 109 /** The wifi icon that should be displayed. */ 110 private val wifiIcon: StateFlow<WifiIcon> = 111 combine( 112 interactor.isEnabled, 113 interactor.isDefault, 114 interactor.isForceHidden, 115 interactor.wifiNetwork, 116 ) { isEnabled, isDefault, isForceHidden, wifiNetwork -> 117 if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) { 118 return@combine WifiIcon.Hidden 119 } 120 121 val icon = wifiNetwork.icon() 122 123 return@combine when { 124 isDefault -> icon 125 wifiConstants.alwaysShowIconIfEnabled -> icon 126 !connectivityConstants.hasDataCapabilities -> icon 127 // See b/272509965: Even if we have an active and validated wifi network, we 128 // don't want to show the icon if wifi isn't the default network. 129 else -> WifiIcon.Hidden 130 } 131 } 132 .logDiffsForTable( 133 wifiTableLogBuffer, 134 columnPrefix = "", 135 initialValue = WifiIcon.Hidden, 136 ) 137 .stateIn( 138 scope, 139 started = SharingStarted.WhileSubscribed(), 140 initialValue = WifiIcon.Hidden 141 ) 142 143 /** The wifi activity status. Null if we shouldn't display the activity status. */ 144 private val activity: Flow<DataActivityModel> = run { 145 val default = DataActivityModel(hasActivityIn = false, hasActivityOut = false) 146 if (!connectivityConstants.shouldShowActivityConfig) { 147 flowOf(default) 148 } else { 149 combine(interactor.activity, interactor.ssid) { activity, ssid -> 150 when (ssid) { 151 null -> default 152 else -> activity 153 } 154 } 155 } 156 .distinctUntilChanged() 157 .logDiffsForTable( 158 wifiTableLogBuffer, 159 columnPrefix = "VM.activity", 160 initialValue = default, 161 ) 162 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = default) 163 } 164 165 private val isActivityInViewVisible: Flow<Boolean> = 166 activity 167 .map { it.hasActivityIn } 168 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) 169 170 private val isActivityOutViewVisible: Flow<Boolean> = 171 activity 172 .map { it.hasActivityOut } 173 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) 174 175 private val isActivityContainerVisible: Flow<Boolean> = 176 combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut -> 177 activityIn || activityOut 178 } 179 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) 180 181 // TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the 182 // airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel 183 // that appropriately knows about both icons and sets the padding appropriately. 184 private val isAirplaneSpacerVisible: Flow<Boolean> = 185 airplaneModeViewModel.isAirplaneModeIconVisible 186 187 /** A view model for the status bar on the home screen. */ 188 val home: HomeWifiViewModel = 189 HomeWifiViewModel( 190 statusBarPipelineFlags, 191 wifiIcon, 192 isActivityInViewVisible, 193 isActivityOutViewVisible, 194 isActivityContainerVisible, 195 isAirplaneSpacerVisible, 196 ) 197 198 /** A view model for the status bar on keyguard. */ 199 val keyguard: KeyguardWifiViewModel = 200 KeyguardWifiViewModel( 201 statusBarPipelineFlags, 202 wifiIcon, 203 isActivityInViewVisible, 204 isActivityOutViewVisible, 205 isActivityContainerVisible, 206 isAirplaneSpacerVisible, 207 ) 208 209 /** A view model for the status bar in quick settings. */ 210 val qs: QsWifiViewModel = 211 QsWifiViewModel( 212 statusBarPipelineFlags, 213 wifiIcon, 214 isActivityInViewVisible, 215 isActivityOutViewVisible, 216 isActivityContainerVisible, 217 isAirplaneSpacerVisible, 218 ) 219 220 companion object { 221 @StringRes 222 @VisibleForTesting 223 internal val NO_INTERNET = R.string.data_connection_no_internet 224 } 225 } 226