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