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.wifi.ui.viewmodel 18 19 import android.content.Context 20 import com.android.systemui.dagger.SysUISingleton 21 import com.android.systemui.dagger.qualifiers.Application 22 import com.android.systemui.log.table.TableLogBuffer 23 import com.android.systemui.log.table.logDiffsForTable 24 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel 25 import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule.Companion.FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON 26 import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog 27 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants 28 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 29 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor 30 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants 31 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel 32 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon 33 import java.util.function.Supplier 34 import javax.inject.Inject 35 import javax.inject.Named 36 import kotlinx.coroutines.CoroutineScope 37 import kotlinx.coroutines.flow.Flow 38 import kotlinx.coroutines.flow.SharingStarted 39 import kotlinx.coroutines.flow.StateFlow 40 import kotlinx.coroutines.flow.combine 41 import kotlinx.coroutines.flow.distinctUntilChanged 42 import kotlinx.coroutines.flow.flowOf 43 import kotlinx.coroutines.flow.map 44 import kotlinx.coroutines.flow.stateIn 45 46 /** 47 * Models the UI state for the status bar wifi icon. 48 * 49 * This is a singleton so that we don't have duplicate logs and should *not* be used directly to 50 * control views. Instead, use an instance of [LocationBasedWifiViewModel]. See 51 * [LocationBasedWifiViewModel.viewModelForLocation]. 52 */ 53 @SysUISingleton 54 class WifiViewModel 55 @Inject 56 constructor( 57 airplaneModeViewModel: AirplaneModeViewModel, 58 // TODO(b/238425913): The wifi icon shouldn't need to consume mobile information. A 59 // container-level view model should do the work instead. 60 @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON) 61 shouldShowSignalSpacerProvider: Supplier<Flow<Boolean>>, 62 connectivityConstants: ConnectivityConstants, 63 private val context: Context, 64 @WifiTableLog wifiTableLogBuffer: TableLogBuffer, 65 interactor: WifiInteractor, 66 @Application private val scope: CoroutineScope, 67 wifiConstants: WifiConstants, 68 ) : WifiViewModelCommon { 69 /** Returns the icon to use based on the given network. */ 70 private fun WifiNetworkModel.icon(): WifiIcon { 71 return WifiIcon.fromModel(this, context) 72 } 73 74 override val wifiIcon: StateFlow<WifiIcon> = 75 combine( 76 interactor.isEnabled, 77 interactor.isDefault, 78 interactor.isForceHidden, 79 interactor.wifiNetwork, 80 ) { isEnabled, isDefault, isForceHidden, wifiNetwork -> 81 if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) { 82 return@combine WifiIcon.Hidden 83 } 84 85 val icon = wifiNetwork.icon() 86 87 return@combine when { 88 isDefault -> icon 89 wifiConstants.alwaysShowIconIfEnabled -> icon 90 !connectivityConstants.hasDataCapabilities -> icon 91 // See b/272509965: Even if we have an active and validated wifi network, we 92 // don't want to show the icon if wifi isn't the default network. 93 else -> WifiIcon.Hidden 94 } 95 } 96 .logDiffsForTable( 97 wifiTableLogBuffer, 98 columnPrefix = "", 99 initialValue = WifiIcon.Hidden, 100 ) 101 .stateIn( 102 scope, 103 started = SharingStarted.WhileSubscribed(), 104 initialValue = WifiIcon.Hidden 105 ) 106 107 /** The wifi activity status. Null if we shouldn't display the activity status. */ 108 private val activity: Flow<DataActivityModel> = run { 109 val default = DataActivityModel(hasActivityIn = false, hasActivityOut = false) 110 if (!connectivityConstants.shouldShowActivityConfig) { 111 flowOf(default) 112 } else { 113 combine(interactor.activity, interactor.ssid) { activity, ssid -> 114 when (ssid) { 115 null -> default 116 else -> activity 117 } 118 } 119 } 120 .distinctUntilChanged() 121 .logDiffsForTable( 122 wifiTableLogBuffer, 123 columnPrefix = "VM.activity", 124 initialValue = default, 125 ) 126 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = default) 127 } 128 129 override val isActivityInViewVisible: Flow<Boolean> = 130 activity 131 .map { it.hasActivityIn } 132 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) 133 134 override val isActivityOutViewVisible: Flow<Boolean> = 135 activity 136 .map { it.hasActivityOut } 137 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) 138 139 override val isActivityContainerVisible: Flow<Boolean> = 140 combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut -> 141 activityIn || activityOut 142 } 143 .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) 144 145 // TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the 146 // airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel 147 // that appropriately knows about both icons and sets the padding appropriately. 148 override val isAirplaneSpacerVisible: Flow<Boolean> = 149 airplaneModeViewModel.isAirplaneModeIconVisible 150 151 override val isSignalSpacerVisible: Flow<Boolean> = shouldShowSignalSpacerProvider.get() 152 } 153