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