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