• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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