• 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.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