• 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.mobile.ui.viewmodel
18 
19 import com.android.systemui.Flags.statusBarStaticInoutIndicators
20 import com.android.systemui.common.shared.model.ContentDescription
21 import com.android.systemui.common.shared.model.Icon
22 import com.android.systemui.log.table.logDiffsForTable
23 import com.android.systemui.res.R
24 import com.android.systemui.statusbar.core.NewStatusBarIcons
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.domain.model.SignalIconModel
29 import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription
30 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
31 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.MutableStateFlow
35 import kotlinx.coroutines.flow.SharingStarted
36 import kotlinx.coroutines.flow.StateFlow
37 import kotlinx.coroutines.flow.combine
38 import kotlinx.coroutines.flow.distinctUntilChanged
39 import kotlinx.coroutines.flow.flatMapLatest
40 import kotlinx.coroutines.flow.flowOf
41 import kotlinx.coroutines.flow.map
42 import kotlinx.coroutines.flow.mapLatest
43 import kotlinx.coroutines.flow.stateIn
44 
45 /** Common interface for all of the location-based mobile icon view models. */
46 interface MobileIconViewModelCommon {
47     val subscriptionId: Int
48     /** True if this view should be visible at all. */
49     val isVisible: StateFlow<Boolean>
50     val icon: Flow<SignalIconModel>
51     val contentDescription: Flow<MobileContentDescription?>
52     val roaming: Flow<Boolean>
53     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
54     val networkTypeIcon: Flow<Icon.Resource?>
55     /** The slice attribution. Drawn as a background layer */
56     val networkTypeBackground: StateFlow<Icon.Resource?>
57     val activityInVisible: Flow<Boolean>
58     val activityOutVisible: Flow<Boolean>
59     val activityContainerVisible: Flow<Boolean>
60 }
61 
62 /**
63  * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
64  * a single line of service via [MobileIconInteractor] and update the UI based on that
65  * subscription's information.
66  *
67  * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
68  * [MobileIconsInteractor.filteredSubscriptions].
69  *
70  * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
71  * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
72  * model gets the exact same information, as well as allows us to log that unified state only once
73  * per icon.
74  */
75 class MobileIconViewModel(
76     override val subscriptionId: Int,
77     iconInteractor: MobileIconInteractor,
78     airplaneModeInteractor: AirplaneModeInteractor,
79     constants: ConnectivityConstants,
80     scope: CoroutineScope,
81 ) : MobileIconViewModelCommon {
<lambda>null82     private val cellProvider by lazy {
83         CellularIconViewModel(
84             subscriptionId,
85             iconInteractor,
86             airplaneModeInteractor,
87             constants,
88             scope,
89         )
90     }
91 
<lambda>null92     private val satelliteProvider by lazy {
93         CarrierBasedSatelliteViewModelImpl(
94             subscriptionId,
95             airplaneModeInteractor,
96             iconInteractor,
97             scope,
98         )
99     }
100 
101     /**
102      * Similar to repository switching, this allows us to split up the logic of satellite/cellular
103      * states, since they are different by nature
104      */
105     private val vmProvider: Flow<MobileIconViewModelCommon> =
106         iconInteractor.isNonTerrestrial
nonTerrestrialnull107             .mapLatest { nonTerrestrial ->
108                 if (nonTerrestrial) {
109                     satelliteProvider
110                 } else {
111                     cellProvider
112                 }
113             }
114             .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider)
115 
116     override val isVisible: StateFlow<Boolean> =
117         vmProvider
<lambda>null118             .flatMapLatest { it.isVisible }
119             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
120 
<lambda>null121     override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }
122 
123     override val contentDescription: Flow<MobileContentDescription?> =
<lambda>null124         vmProvider.flatMapLatest { it.contentDescription }
125 
<lambda>null126     override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }
127 
128     override val networkTypeIcon: Flow<Icon.Resource?> =
<lambda>null129         vmProvider.flatMapLatest { it.networkTypeIcon }
130 
131     override val networkTypeBackground: StateFlow<Icon.Resource?> =
132         vmProvider
<lambda>null133             .flatMapLatest { it.networkTypeBackground }
134             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
135 
136     override val activityInVisible: Flow<Boolean> =
<lambda>null137         vmProvider.flatMapLatest { it.activityInVisible }
138 
139     override val activityOutVisible: Flow<Boolean> =
<lambda>null140         vmProvider.flatMapLatest { it.activityOutVisible }
141 
142     override val activityContainerVisible: Flow<Boolean> =
<lambda>null143         vmProvider.flatMapLatest { it.activityContainerVisible }
144 }
145 
146 /** Representation of this network when it is non-terrestrial (e.g., satellite) */
147 private class CarrierBasedSatelliteViewModelImpl(
148     override val subscriptionId: Int,
149     airplaneModeInteractor: AirplaneModeInteractor,
150     interactor: MobileIconInteractor,
151     scope: CoroutineScope,
152 ) : MobileIconViewModelCommon {
153     override val isVisible: StateFlow<Boolean> =
154         airplaneModeInteractor.isAirplaneMode
<lambda>null155             .map { !it }
156             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
157 
158     override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon
159 
160     override val contentDescription: Flow<MobileContentDescription?> = MutableStateFlow(null)
161 
162     /** These fields are not used for satellite icons currently */
163     override val roaming: Flow<Boolean> = flowOf(false)
164     override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null)
165     override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null)
166     override val activityInVisible: Flow<Boolean> = flowOf(false)
167     override val activityOutVisible: Flow<Boolean> = flowOf(false)
168     override val activityContainerVisible: Flow<Boolean> = flowOf(false)
169 }
170 
171 /** Terrestrial (cellular) icon. */
172 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
173 private class CellularIconViewModel(
174     override val subscriptionId: Int,
175     iconInteractor: MobileIconInteractor,
176     airplaneModeInteractor: AirplaneModeInteractor,
177     constants: ConnectivityConstants,
178     scope: CoroutineScope,
179 ) : MobileIconViewModelCommon {
180     override val isVisible: StateFlow<Boolean> =
181         if (!constants.hasDataCapabilities) {
182                 flowOf(false)
183             } else {
184                 combine(
185                     airplaneModeInteractor.isAirplaneMode,
186                     iconInteractor.isAllowedDuringAirplaneMode,
187                     iconInteractor.isForceHidden,
isAllowedDuringAirplaneModenull188                 ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden ->
189                     if (isForceHidden) {
190                         false
191                     } else if (isAirplaneMode) {
192                         isAllowedDuringAirplaneMode
193                     } else {
194                         true
195                     }
196                 }
197             }
198             .distinctUntilChanged()
199             .logDiffsForTable(
200                 iconInteractor.tableLogBuffer,
201                 columnName = "visible",
202                 initialValue = false,
203             )
204             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
205 
206     override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon
207 
208     override val contentDescription: Flow<MobileContentDescription?> =
nameModelnull209         combine(iconInteractor.signalLevelIcon, iconInteractor.networkName) { icon, nameModel ->
210                 when (icon) {
211                     is SignalIconModel.Cellular ->
212                         MobileContentDescription.Cellular(
213                             nameModel.name,
214                             icon.levelDescriptionRes(),
215                         )
216                     else -> null
217                 }
218             }
219             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
220 
levelDescriptionResnull221     private fun SignalIconModel.Cellular.levelDescriptionRes() =
222         when (level) {
223             0 -> R.string.accessibility_no_signal
224             1 -> R.string.accessibility_one_bar
225             2 -> R.string.accessibility_two_bars
226             3 -> R.string.accessibility_three_bars
227             4 -> {
228                 if (numberOfLevels == 6) {
229                     R.string.accessibility_four_bars
230                 } else {
231                     R.string.accessibility_signal_full
232                 }
233             }
234             5 -> {
235                 if (numberOfLevels == 6) {
236                     R.string.accessibility_signal_full
237                 } else {
238                     R.string.accessibility_no_signal
239                 }
240             }
241             else -> R.string.accessibility_no_signal
242         }
243 
244     private val showNetworkTypeIcon: Flow<Boolean> =
245         combine(
246                 iconInteractor.isDataConnected,
247                 iconInteractor.isDataEnabled,
248                 iconInteractor.alwaysShowDataRatIcon,
249                 iconInteractor.mobileIsDefault,
250                 iconInteractor.carrierNetworkChangeActive,
dataConnectednull251             ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault, carrierNetworkChange ->
252                 alwaysShow ||
253                     (!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault))
254             }
255             .distinctUntilChanged()
256             .logDiffsForTable(
257                 iconInteractor.tableLogBuffer,
258                 columnName = "showNetworkTypeIcon",
259                 initialValue = false,
260             )
261             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
262 
263     override val networkTypeIcon: Flow<Icon.Resource?> =
264         combine(iconInteractor.networkTypeIconGroup, showNetworkTypeIcon) {
networkTypeIconGroupnull265                 networkTypeIconGroup,
266                 shouldShow ->
267                 val desc =
268                     if (networkTypeIconGroup.contentDescription != 0)
269                         ContentDescription.Resource(networkTypeIconGroup.contentDescription)
270                     else null
271                 val icon =
272                     if (networkTypeIconGroup.iconId != 0)
273                         Icon.Resource(networkTypeIconGroup.iconId, desc)
274                     else null
275                 return@combine when {
276                     !shouldShow -> null
277                     else -> icon
278                 }
279             }
280             .distinctUntilChanged()
281             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
282 
283     override val networkTypeBackground =
284         iconInteractor.showSliceAttribution
<lambda>null285             .map {
286                 when {
287                     it && NewStatusBarIcons.isEnabled ->
288                         Icon.Resource(R.drawable.mobile_network_type_background_updated, null)
289                     it -> Icon.Resource(R.drawable.mobile_network_type_background, null)
290                     else -> null
291                 }
292             }
293             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
294 
295     override val roaming: StateFlow<Boolean> =
296         iconInteractor.isRoaming
297             .logDiffsForTable(
298                 iconInteractor.tableLogBuffer,
299                 columnName = "roaming",
300                 initialValue = false,
301             )
302             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
303 
304     private val activity: Flow<DataActivityModel?> =
305         if (!constants.shouldShowActivityConfig) {
306             flowOf(null)
307         } else {
308             iconInteractor.activity
309         }
310 
311     override val activityInVisible: Flow<Boolean> =
312         activity
<lambda>null313             .map { it?.hasActivityIn ?: false }
314             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
315 
316     override val activityOutVisible: Flow<Boolean> =
317         activity
<lambda>null318             .map { it?.hasActivityOut ?: false }
319             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
320 
321     override val activityContainerVisible: Flow<Boolean> =
322         if (statusBarStaticInoutIndicators()) {
323                 flowOf(constants.shouldShowActivityConfig)
324             } else {
<lambda>null325                 activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
326             }
327             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
328 }
329