• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.KairosBuilder
21 import com.android.systemui.activated
22 import com.android.systemui.common.shared.model.ContentDescription
23 import com.android.systemui.common.shared.model.Icon
24 import com.android.systemui.flags.FeatureFlagsClassic
25 import com.android.systemui.kairos.ExperimentalKairosApi
26 import com.android.systemui.kairos.State as KairosState
27 import com.android.systemui.kairos.State
28 import com.android.systemui.kairos.combine
29 import com.android.systemui.kairos.flatMap
30 import com.android.systemui.kairos.map
31 import com.android.systemui.kairos.stateOf
32 import com.android.systemui.kairosBuilder
33 import com.android.systemui.log.table.logDiffsForTable
34 import com.android.systemui.res.R
35 import com.android.systemui.statusbar.core.NewStatusBarIcons
36 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
37 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorKairos
38 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
39 import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription
40 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
41 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
42 
43 /** Common interface for all of the location-based mobile icon view models. */
44 @ExperimentalKairosApi
45 interface MobileIconViewModelKairosCommon {
46     val subscriptionId: Int
47     val iconInteractor: MobileIconInteractorKairos
48     /** True if this view should be visible at all. */
49     val isVisible: KairosState<Boolean>
50     val icon: KairosState<SignalIconModel>
51     val contentDescription: KairosState<MobileContentDescription?>
52     val roaming: KairosState<Boolean>
53     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
54     val networkTypeIcon: KairosState<Icon.Resource?>
55     /** The slice attribution. Drawn as a background layer */
56     val networkTypeBackground: KairosState<Icon.Resource?>
57     val activityInVisible: KairosState<Boolean>
58     val activityOutVisible: KairosState<Boolean>
59     val activityContainerVisible: KairosState<Boolean>
60 }
61 
62 /**
63  * View model for the state of a single mobile icon. Each [MobileIconViewModelKairos] will keep
64  * watch over a single line of service via [MobileIconInteractorKairos] and update the UI based on
65  * that subscription's information.
66  *
67  * There will be exactly one [MobileIconViewModelKairos] per filtered subscription offered from
68  * [MobileIconsInteractorKairos.filteredSubscriptions].
69  */
70 @ExperimentalKairosApi
71 class MobileIconViewModelKairos(
72     override val subscriptionId: Int,
73     override val iconInteractor: MobileIconInteractorKairos,
74     private val airplaneModeInteractor: AirplaneModeInteractor,
75     private val constants: ConnectivityConstants,
76     private val flags: FeatureFlagsClassic,
<lambda>null77 ) : MobileIconViewModelKairosCommon, KairosBuilder by kairosBuilder() {
78 
79     private val isAirplaneMode: State<Boolean> = buildState {
80         airplaneModeInteractor.isAirplaneMode.toState()
81     }
82 
83     private val satelliteProvider by lazy {
84         CarrierBasedSatelliteViewModelKairosImpl(subscriptionId, iconInteractor, isAirplaneMode)
85     }
86 
87     /**
88      * Similar to repository switching, this allows us to split up the logic of satellite/cellular
89      * states, since they are different by nature
90      */
91     private val vmProvider: KairosState<MobileIconViewModelKairosCommon> = buildState {
92         iconInteractor.isNonTerrestrial.mapLatestBuild { nonTerrestrial ->
93             if (nonTerrestrial) {
94                 satelliteProvider
95             } else {
96                 activated {
97                     CellularIconViewModelKairos(
98                         subscriptionId,
99                         iconInteractor,
100                         airplaneModeInteractor,
101                         constants,
102                         flags,
103                     )
104                 }
105             }
106         }
107     }
108 
109     override val isVisible: KairosState<Boolean> = vmProvider.flatMap { it.isVisible }
110 
111     override val icon: KairosState<SignalIconModel> = vmProvider.flatMap { it.icon }
112 
113     override val contentDescription: KairosState<MobileContentDescription?> =
114         vmProvider.flatMap { it.contentDescription }
115 
116     override val roaming: KairosState<Boolean> = vmProvider.flatMap { it.roaming }
117 
118     override val networkTypeIcon: KairosState<Icon.Resource?> =
119         vmProvider.flatMap { it.networkTypeIcon }
120 
121     override val networkTypeBackground: KairosState<Icon.Resource?> =
122         vmProvider.flatMap { it.networkTypeBackground }
123 
124     override val activityInVisible: KairosState<Boolean> =
125         vmProvider.flatMap { it.activityInVisible }
126 
127     override val activityOutVisible: KairosState<Boolean> =
128         vmProvider.flatMap { it.activityOutVisible }
129 
130     override val activityContainerVisible: KairosState<Boolean> =
131         vmProvider.flatMap { it.activityContainerVisible }
132 }
133 
134 /** Representation of this network when it is non-terrestrial (e.g., satellite) */
135 @ExperimentalKairosApi
136 private class CarrierBasedSatelliteViewModelKairosImpl(
137     override val subscriptionId: Int,
138     override val iconInteractor: MobileIconInteractorKairos,
139     isAirplaneMode: KairosState<Boolean>,
140 ) : MobileIconViewModelKairosCommon {
<lambda>null141     override val isVisible: KairosState<Boolean> = isAirplaneMode.map { !it }
142     override val icon: KairosState<SignalIconModel>
143         get() = iconInteractor.signalLevelIcon
144 
145     override val contentDescription: KairosState<MobileContentDescription?> = stateOf(null)
146 
147     /** These fields are not used for satellite icons currently */
148     override val roaming: KairosState<Boolean> = stateOf(false)
149     override val networkTypeIcon: KairosState<Icon.Resource?> = stateOf(null)
150     override val networkTypeBackground: KairosState<Icon.Resource?> = stateOf(null)
151     override val activityInVisible: KairosState<Boolean> = stateOf(false)
152     override val activityOutVisible: KairosState<Boolean> = stateOf(false)
153     override val activityContainerVisible: KairosState<Boolean> = stateOf(false)
154 }
155 
156 /** Terrestrial (cellular) icon. */
157 @ExperimentalKairosApi
158 private class CellularIconViewModelKairos(
159     override val subscriptionId: Int,
160     override val iconInteractor: MobileIconInteractorKairos,
161     airplaneModeInteractor: AirplaneModeInteractor,
162     constants: ConnectivityConstants,
163     flags: FeatureFlagsClassic,
<lambda>null164 ) : MobileIconViewModelKairosCommon, KairosBuilder by kairosBuilder() {
165 
166     override val isVisible: KairosState<Boolean> =
167         if (!constants.hasDataCapabilities) {
168             stateOf(false)
169         } else {
170             buildState {
171                 combine(
172                         airplaneModeInteractor.isAirplaneMode.toState(),
173                         iconInteractor.isAllowedDuringAirplaneMode,
174                         iconInteractor.isForceHidden,
175                     ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden ->
176                         if (isForceHidden) {
177                             false
178                         } else if (isAirplaneMode) {
179                             isAllowedDuringAirplaneMode
180                         } else {
181                             true
182                         }
183                     }
184                     .also {
185                         logDiffsForTable(it, iconInteractor.tableLogBuffer, columnName = "visible")
186                     }
187             }
188         }
189 
190     override val icon: KairosState<SignalIconModel>
191         get() = iconInteractor.signalLevelIcon
192 
193     override val contentDescription: KairosState<MobileContentDescription?> =
194         combine(iconInteractor.signalLevelIcon, iconInteractor.networkName) { icon, nameModel ->
195             when (icon) {
196                 is SignalIconModel.Cellular ->
197                     MobileContentDescription.Cellular(nameModel.name, icon.levelDescriptionRes())
198                 else -> null
199             }
200         }
201 
202     private fun SignalIconModel.Cellular.levelDescriptionRes() =
203         when (level) {
204             0 -> R.string.accessibility_no_signal
205             1 -> R.string.accessibility_one_bar
206             2 -> R.string.accessibility_two_bars
207             3 -> R.string.accessibility_three_bars
208             4 -> {
209                 if (numberOfLevels == 6) {
210                     R.string.accessibility_four_bars
211                 } else {
212                     R.string.accessibility_signal_full
213                 }
214             }
215             5 -> {
216                 if (numberOfLevels == 6) {
217                     R.string.accessibility_signal_full
218                 } else {
219                     R.string.accessibility_no_signal
220                 }
221             }
222             else -> R.string.accessibility_no_signal
223         }
224 
225     private val showNetworkTypeIcon: KairosState<Boolean> =
226         combine(
227                 iconInteractor.isDataConnected,
228                 iconInteractor.isDataEnabled,
229                 iconInteractor.alwaysShowDataRatIcon,
230                 iconInteractor.mobileIsDefault,
231                 iconInteractor.carrierNetworkChangeActive,
232             ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault, carrierNetworkChange ->
233                 alwaysShow ||
234                     (!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault))
235             }
236             .also {
237                 onActivated {
238                     logDiffsForTable(
239                         it,
240                         iconInteractor.tableLogBuffer,
241                         columnName = "showNetworkTypeIcon",
242                     )
243                 }
244             }
245 
246     override val networkTypeIcon: KairosState<Icon.Resource?> =
247         combine(iconInteractor.networkTypeIconGroup, showNetworkTypeIcon) {
248             networkTypeIconGroup,
249             shouldShow ->
250             val desc =
251                 if (networkTypeIconGroup.contentDescription != 0) {
252                     ContentDescription.Resource(networkTypeIconGroup.contentDescription)
253                 } else {
254                     null
255                 }
256             val icon =
257                 if (networkTypeIconGroup.iconId != 0) {
258                     Icon.Resource(networkTypeIconGroup.iconId, desc)
259                 } else {
260                     null
261                 }
262             when {
263                 !shouldShow -> null
264                 else -> icon
265             }
266         }
267 
268     override val networkTypeBackground: KairosState<Icon.Resource?> =
269         iconInteractor.showSliceAttribution.map {
270             when {
271                 it && NewStatusBarIcons.isEnabled ->
272                     Icon.Resource(R.drawable.mobile_network_type_background_updated, null)
273                 it -> Icon.Resource(R.drawable.mobile_network_type_background, null)
274                 else -> null
275             }
276         }
277 
278     override val roaming: KairosState<Boolean> =
279         iconInteractor.isRoaming.also {
280             onActivated {
281                 logDiffsForTable(it, iconInteractor.tableLogBuffer, columnName = "roaming")
282             }
283         }
284 
285     private val activity: KairosState<DataActivityModel?> =
286         if (!constants.shouldShowActivityConfig) {
287             stateOf(null)
288         } else {
289             iconInteractor.activity
290         }
291 
292     override val activityInVisible: KairosState<Boolean> =
293         activity.map { it?.hasActivityIn ?: false }
294 
295     override val activityOutVisible: KairosState<Boolean> =
296         activity.map { it?.hasActivityOut ?: false }
297 
298     override val activityContainerVisible: KairosState<Boolean> =
299         if (statusBarStaticInoutIndicators()) {
300             stateOf(constants.shouldShowActivityConfig)
301         } else {
302             activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
303         }
304 }
305