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