• 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.telephony.CarrierConfigManager
20 import com.android.settingslib.SignalIcon.MobileIconGroup
21 import com.android.settingslib.mobile.TelephonyIcons.NOT_DEFAULT_DATA
22 import com.android.systemui.dagger.qualifiers.Application
23 import com.android.systemui.log.table.TableLogBuffer
24 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
25 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
26 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
27 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
28 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
29 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
30 import kotlinx.coroutines.CoroutineScope
31 import kotlinx.coroutines.ExperimentalCoroutinesApi
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.SharingStarted
34 import kotlinx.coroutines.flow.StateFlow
35 import kotlinx.coroutines.flow.combine
36 import kotlinx.coroutines.flow.distinctUntilChanged
37 import kotlinx.coroutines.flow.map
38 import kotlinx.coroutines.flow.mapLatest
39 import kotlinx.coroutines.flow.onEach
40 import kotlinx.coroutines.flow.stateIn
41 
42 interface MobileIconInteractor {
43     /** The table log created for this connection */
44     val tableLogBuffer: TableLogBuffer
45 
46     /** The current mobile data activity */
47     val activity: Flow<DataActivityModel>
48 
49     /**
50      * This bit is meant to be `true` if and only if the default network capabilities (see
51      * [android.net.ConnectivityManager.registerDefaultNetworkCallback]) result in a network that
52      * has the [android.net.NetworkCapabilities.TRANSPORT_CELLULAR] represented.
53      *
54      * Note that this differs from [isDataConnected], which is tracked by telephony and has to do
55      * with the state of using this mobile connection for data as opposed to just voice. It is
56      * possible for a mobile subscription to be connected but not be in a connected data state, and
57      * thus we wouldn't want to show the network type icon.
58      */
59     val isConnected: 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     /** Only true if mobile is the default transport but is not validated, otherwise false */
70     val isDefaultConnectionFailed: StateFlow<Boolean>
71 
72     /** True if we consider this connection to be in service, i.e. can make calls */
73     val isInService: StateFlow<Boolean>
74 
75     // TODO(b/256839546): clarify naming of default vs active
76     /** True if we want to consider the data connection enabled */
77     val isDefaultDataEnabled: StateFlow<Boolean>
78 
79     /** Observable for the data enabled state of this connection */
80     val isDataEnabled: StateFlow<Boolean>
81 
82     /** True if the RAT icon should always be displayed and false otherwise. */
83     val alwaysShowDataRatIcon: StateFlow<Boolean>
84 
85     /** True if the CDMA level should be preferred over the primary level. */
86     val alwaysUseCdmaLevel: StateFlow<Boolean>
87 
88     /** Observable for RAT type (network type) indicator */
89     val networkTypeIconGroup: StateFlow<MobileIconGroup>
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: StateFlow<NetworkNameModel>
100 
101     /** True if this line of service is emergency-only */
102     val isEmergencyOnly: StateFlow<Boolean>
103 
104     /**
105      * True if this connection is considered roaming. The roaming bit can come from [ServiceState],
106      * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
107      * connection to be roaming while carrier network change is active
108      */
109     val isRoaming: StateFlow<Boolean>
110 
111     /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
112     val level: StateFlow<Int>
113 
114     /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
115     val numberOfLevels: StateFlow<Int>
116 
117     /** See [MobileIconsInteractor.isForceHidden]. */
118     val isForceHidden: Flow<Boolean>
119 }
120 
121 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
122 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
123 @OptIn(ExperimentalCoroutinesApi::class)
124 class MobileIconInteractorImpl(
125     @Application scope: CoroutineScope,
126     defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
127     override val alwaysShowDataRatIcon: StateFlow<Boolean>,
128     override val alwaysUseCdmaLevel: StateFlow<Boolean>,
129     defaultMobileConnectivity: StateFlow<MobileConnectivityModel>,
130     defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
131     defaultMobileIconGroup: StateFlow<MobileIconGroup>,
132     defaultDataSubId: StateFlow<Int>,
133     override val isDefaultConnectionFailed: StateFlow<Boolean>,
134     override val isForceHidden: Flow<Boolean>,
135     connectionRepository: MobileConnectionRepository,
136 ) : MobileIconInteractor {
137     override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
138 
139     override val activity = connectionRepository.dataActivityDirection
140 
<lambda>null141     override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
142 
143     override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
144 
145     private val isDefault =
146         defaultDataSubId
<lambda>null147             .mapLatest { connectionRepository.subId == it }
148             .stateIn(
149                 scope,
150                 SharingStarted.WhileSubscribed(),
151                 connectionRepository.subId == defaultDataSubId.value
152             )
153 
154     override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
155 
156     override val networkName =
157         combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
operatorAlphaShortnull158                 operatorAlphaShort,
159                 networkName ->
160                 if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
161                     NetworkNameModel.IntentDerived(operatorAlphaShort)
162                 } else {
163                     networkName
164                 }
165             }
166             .stateIn(
167                 scope,
168                 SharingStarted.WhileSubscribed(),
169                 connectionRepository.networkName.value
170             )
171 
172     /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
173     override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
174         combine(
175                 connectionRepository.resolvedNetworkType,
176                 defaultMobileIconMapping,
177                 defaultMobileIconGroup,
178                 isDefault,
mappingnull179             ) { resolvedNetworkType, mapping, defaultGroup, isDefault ->
180                 if (!isDefault) {
181                     return@combine NOT_DEFAULT_DATA
182                 }
183 
184                 when (resolvedNetworkType) {
185                     is ResolvedNetworkType.CarrierMergedNetworkType ->
186                         resolvedNetworkType.iconGroupOverride
187                     else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
188                 }
189             }
190             .distinctUntilChanged()
<lambda>null191             .onEach {
192                 // Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the
193                 // [Diffable] interface.
194                 tableLogBuffer.logChange(
195                     prefix = "",
196                     columnName = "networkTypeIcon",
197                     value = it.name
198                 )
199             }
200             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
201 
202     override val isEmergencyOnly = connectionRepository.isEmergencyOnly
203 
204     override val isRoaming: StateFlow<Boolean> =
205         combine(
206                 connectionRepository.carrierNetworkChangeActive,
207                 connectionRepository.isGsm,
208                 connectionRepository.isRoaming,
209                 connectionRepository.cdmaRoaming,
isRoamingnull210             ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
211                 if (carrierNetworkChangeActive) {
212                     false
213                 } else if (isGsm) {
214                     isRoaming
215                 } else {
216                     cdmaRoaming
217                 }
218             }
219             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
220 
221     override val level: StateFlow<Int> =
222         combine(
223                 connectionRepository.isGsm,
224                 connectionRepository.primaryLevel,
225                 connectionRepository.cdmaLevel,
226                 alwaysUseCdmaLevel,
primaryLevelnull227             ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
228                 when {
229                     // GSM connections should never use the CDMA level
230                     isGsm -> primaryLevel
231                     alwaysUseCdmaLevel -> cdmaLevel
232                     else -> primaryLevel
233                 }
234             }
235             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
236 
237     override val numberOfLevels: StateFlow<Int> =
238         connectionRepository.numberOfLevels.stateIn(
239             scope,
240             SharingStarted.WhileSubscribed(),
241             connectionRepository.numberOfLevels.value,
242         )
243 
244     override val isDataConnected: StateFlow<Boolean> =
245         connectionRepository.dataConnectionState
<lambda>null246             .map { it == Connected }
247             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
248 
249     override val isInService = connectionRepository.isInService
250 }
251