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.domain.interactor
18
19 import android.content.Context
20 import com.android.internal.telephony.flags.Flags
21 import com.android.settingslib.SignalIcon.MobileIconGroup
22 import com.android.settingslib.graph.SignalDrawable
23 import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
24 import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
25 import com.android.systemui.KairosBuilder
26 import com.android.systemui.kairos.ExperimentalKairosApi
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.kairosBuilder
32 import com.android.systemui.log.table.TableLogBuffer
33 import com.android.systemui.log.table.logDiffsForTable
34 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
35 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
36 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
37 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
38 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
39 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
40 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
41 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
42 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
43 import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
44 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
45
46 @ExperimentalKairosApi
47 interface MobileIconInteractorKairos {
48 /** The table log created for this connection */
49 val tableLogBuffer: TableLogBuffer
50
51 val subscriptionId: Int
52
53 /** The current mobile data activity */
54 val activity: State<DataActivityModel>
55
56 /** See [MobileConnectionsRepository.mobileIsDefault]. */
57 val mobileIsDefault: State<Boolean>
58
59 /**
60 * True when telephony tells us that the data state is CONNECTED. See
61 * [android.telephony.TelephonyCallback.DataConnectionStateListener] for more details. We
62 * consider this connection to be serving data, and thus want to show a network type icon, when
63 * data is connected. Other data connection states would typically cause us not to show the icon
64 */
65 val isDataConnected: State<Boolean>
66
67 /** True if we consider this connection to be in service, i.e. can make calls */
68 val isInService: State<Boolean>
69
70 /** True if this connection is emergency only */
71 val isEmergencyOnly: State<Boolean>
72
73 /** Observable for the data enabled state of this connection */
74 val isDataEnabled: State<Boolean>
75
76 /** True if the RAT icon should always be displayed and false otherwise. */
77 val alwaysShowDataRatIcon: State<Boolean>
78
79 /** Canonical representation of the current mobile signal strength as a triangle. */
80 val signalLevelIcon: State<SignalIconModel>
81
82 /** Observable for RAT type (network type) indicator */
83 val networkTypeIconGroup: State<NetworkTypeIconModel>
84
85 /** Whether or not to show the slice attribution */
86 val showSliceAttribution: State<Boolean>
87
88 /** True if this connection is satellite-based */
89 val isNonTerrestrial: State<Boolean>
90
91 /**
92 * Provider name for this network connection. The name can be one of 3 values:
93 * 1. The default network name, if one is configured
94 * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED]
95 * 3. Or, in the case where the repository sends us the default network name, we check for an
96 * override in [connectionInfo.operatorAlphaShort], a value that is derived from
97 * [ServiceState]
98 */
99 val networkName: State<NetworkNameModel>
100
101 /**
102 * Provider name for this network connection. The name can be one of 3 values:
103 * 1. The default network name, if one is configured
104 * 2. A name provided by the [SubscriptionModel] of this network connection
105 * 3. Or, in the case where the repository sends us the default network name, we check for an
106 * override in [connectionInfo.operatorAlphaShort], a value that is derived from
107 * [ServiceState]
108 *
109 * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
110 * provided is identical
111 */
112 val carrierName: State<String>
113
114 /** True if there is only one active subscription. */
115 val isSingleCarrier: State<Boolean>
116
117 /**
118 * True if this connection is considered roaming. The roaming bit can come from [ServiceState],
119 * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
120 * connection to be roaming while carrier network change is active
121 */
122 val isRoaming: State<Boolean>
123
124 /** See [MobileIconsInteractor.isForceHidden]. */
125 val isForceHidden: State<Boolean>
126
127 /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
128 val isAllowedDuringAirplaneMode: State<Boolean>
129
130 /** True when in carrier network change mode */
131 val carrierNetworkChangeActive: State<Boolean>
132 }
133
134 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
135 @ExperimentalKairosApi
136 class MobileIconInteractorKairosImpl(
137 defaultSubscriptionHasDataEnabled: State<Boolean>,
138 override val alwaysShowDataRatIcon: State<Boolean>,
139 alwaysUseCdmaLevel: State<Boolean>,
140 override val isSingleCarrier: State<Boolean>,
141 override val mobileIsDefault: State<Boolean>,
142 defaultMobileIconMapping: State<Map<String, MobileIconGroup>>,
143 defaultMobileIconGroup: State<MobileIconGroup>,
144 isDefaultConnectionFailed: State<Boolean>,
145 override val isForceHidden: State<Boolean>,
146 private val connectionRepository: MobileConnectionRepositoryKairos,
147 private val context: Context,
148 private val carrierIdOverrides: MobileIconCarrierIdOverrides =
149 MobileIconCarrierIdOverridesImpl(),
<lambda>null150 ) : MobileIconInteractorKairos, KairosBuilder by kairosBuilder() {
151 override val subscriptionId: Int
152 get() = connectionRepository.subId
153
154 override val tableLogBuffer: TableLogBuffer
155 get() = connectionRepository.tableLogBuffer
156
157 override val activity: State<DataActivityModel>
158 get() = connectionRepository.dataActivityDirection
159
160 override val isDataEnabled: State<Boolean> = connectionRepository.dataEnabled
161
162 override val carrierNetworkChangeActive: State<Boolean>
163 get() = connectionRepository.carrierNetworkChangeActive
164
165 // True if there exists _any_ icon override for this carrierId. Note that overrides can include
166 // any or none of the icon groups defined in MobileMappings, so we still need to check on a
167 // per-network-type basis whether or not the given icon group is overridden
168 private val carrierIdIconOverrideExists: State<Boolean> =
169 connectionRepository.carrierId.map { carrierIdOverrides.carrierIdEntryExists(it) }
170
171 override val networkName: State<NetworkNameModel> =
172 combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
173 operatorAlphaShort,
174 networkName ->
175 if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
176 NetworkNameModel.IntentDerived(operatorAlphaShort)
177 } else {
178 networkName
179 }
180 }
181
182 override val carrierName: State<String> =
183 combine(connectionRepository.operatorAlphaShort, connectionRepository.carrierName) {
184 operatorAlphaShort,
185 networkName ->
186 if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
187 operatorAlphaShort
188 } else {
189 networkName.name
190 }
191 }
192
193 /** What the mobile icon would be before carrierId overrides */
194 private val defaultNetworkType: State<MobileIconGroup> =
195 combine(
196 connectionRepository.resolvedNetworkType,
197 defaultMobileIconMapping,
198 defaultMobileIconGroup,
199 ) { resolvedNetworkType, mapping, defaultGroup ->
200 when (resolvedNetworkType) {
201 is ResolvedNetworkType.CarrierMergedNetworkType ->
202 resolvedNetworkType.iconGroupOverride
203
204 else -> {
205 mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
206 }
207 }
208 }
209
210 override val networkTypeIconGroup: State<NetworkTypeIconModel> = buildState {
211 combineTransactionally(defaultNetworkType, carrierIdIconOverrideExists) {
212 networkType,
213 overrideExists ->
214 // DefaultIcon comes out of the icongroup lookup, we check for overrides here
215 if (overrideExists) {
216 val iconOverride =
217 carrierIdOverrides.getOverrideFor(
218 connectionRepository.carrierId.sample(),
219 networkType.name,
220 context.resources,
221 )
222 if (iconOverride > 0) {
223 OverriddenIcon(networkType, iconOverride)
224 } else {
225 DefaultIcon(networkType)
226 }
227 } else {
228 DefaultIcon(networkType)
229 }
230 }
231 .also { logDiffsForTable(it, tableLogBuffer = tableLogBuffer) }
232 }
233
234 override val showSliceAttribution: State<Boolean> =
235 combine(
236 connectionRepository.allowNetworkSliceIndicator,
237 connectionRepository.hasPrioritizedNetworkCapabilities,
238 ) { allowed, hasPrioritizedNetworkCapabilities ->
239 allowed && hasPrioritizedNetworkCapabilities
240 }
241
242 override val isNonTerrestrial: State<Boolean>
243 get() = connectionRepository.isNonTerrestrial
244
245 override val isRoaming: State<Boolean> =
246 combine(
247 connectionRepository.carrierNetworkChangeActive,
248 connectionRepository.isGsm,
249 connectionRepository.isRoaming,
250 connectionRepository.cdmaRoaming,
251 ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
252 if (carrierNetworkChangeActive) {
253 false
254 } else if (isGsm) {
255 isRoaming
256 } else {
257 cdmaRoaming
258 }
259 }
260
261 private val level: State<Int> =
262 combine(
263 connectionRepository.isGsm,
264 connectionRepository.primaryLevel,
265 connectionRepository.cdmaLevel,
266 alwaysUseCdmaLevel,
267 ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
268 when {
269 // GSM connections should never use the CDMA level
270 isGsm -> primaryLevel
271 alwaysUseCdmaLevel -> cdmaLevel
272 else -> primaryLevel
273 }
274 }
275
276 private val numberOfLevels: State<Int>
277 get() = connectionRepository.numberOfLevels
278
279 override val isDataConnected: State<Boolean> =
280 connectionRepository.dataConnectionState
281 .map { it == Connected }
282 .also {
283 onActivated { logDiffsForTable(it, tableLogBuffer, "icon", "isDataConnected") }
284 }
285
286 override val isInService
287 get() = connectionRepository.isInService
288
289 override val isEmergencyOnly: State<Boolean>
290 get() = connectionRepository.isEmergencyOnly
291
292 override val isAllowedDuringAirplaneMode: State<Boolean>
293 get() = connectionRepository.isAllowedDuringAirplaneMode
294
295 /** Whether or not to show the error state of [SignalDrawable] */
296 private val showExclamationMark: State<Boolean> =
297 combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) {
298 isDefaultDataEnabled,
299 isDefaultConnectionFailed,
300 isInService ->
301 !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
302 }
303
304 private val cellularShownLevel: State<Int> =
305 combine(level, isInService, connectionRepository.inflateSignalStrength) {
306 level,
307 isInService,
308 inflate ->
309 when {
310 !isInService -> 0
311 inflate -> level + 1
312 else -> level
313 }
314 }
315
316 // Satellite level is unaffected by the inflateSignalStrength property
317 // See b/346904529 for details
318 private val satelliteShownLevel: State<Int> =
319 if (Flags.carrierRoamingNbIotNtn()) {
320 connectionRepository.satelliteLevel
321 } else {
322 combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
323 }
324
325 private val cellularIcon: State<SignalIconModel.Cellular> =
326 combine(
327 cellularShownLevel,
328 numberOfLevels,
329 showExclamationMark,
330 carrierNetworkChangeActive,
331 ) { cellularShownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
332 SignalIconModel.Cellular(
333 cellularShownLevel,
334 numberOfLevels,
335 showExclamationMark,
336 carrierNetworkChange,
337 )
338 }
339
340 private val satelliteIcon: State<SignalIconModel.Satellite> =
341 satelliteShownLevel.map {
342 SignalIconModel.Satellite(
343 level = it,
344 icon =
345 SatelliteIconModel.fromSignalStrength(it)
346 ?: SatelliteIconModel.fromSignalStrength(0)!!,
347 )
348 }
349
350 override val signalLevelIcon: State<SignalIconModel> =
351 isNonTerrestrial
352 .flatMap { ntn ->
353 if (ntn) {
354 satelliteIcon
355 } else {
356 cellularIcon
357 }
358 }
359 .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "icon") } }
360 }
361