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