• 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.shared.data.repository
18 
19 import android.annotation.SuppressLint
20 import android.content.Context
21 import android.net.ConnectivityManager
22 import android.net.Network
23 import android.net.NetworkCapabilities
24 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
25 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
26 import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
27 import android.net.NetworkCapabilities.TRANSPORT_WIFI
28 import android.net.vcn.VcnTransportInfo
29 import android.net.vcn.VcnUtils
30 import android.net.wifi.WifiInfo
31 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
32 import androidx.annotation.ArrayRes
33 import androidx.annotation.VisibleForTesting
34 import com.android.systemui.Dumpable
35 import com.android.systemui.Flags
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.dagger.qualifiers.Application
38 import com.android.systemui.dump.DumpManager
39 import com.android.systemui.res.R
40 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
41 import com.android.systemui.statusbar.pipeline.shared.ConnectivityInputLogger
42 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
43 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
44 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
45 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.CarrierMerged
46 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet
47 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile
48 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
49 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
50 import com.android.systemui.tuner.TunerService
51 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
52 import java.io.PrintWriter
53 import javax.inject.Inject
54 import kotlinx.coroutines.CoroutineScope
55 import kotlinx.coroutines.channels.awaitClose
56 import kotlinx.coroutines.flow.SharedFlow
57 import kotlinx.coroutines.flow.SharingStarted
58 import kotlinx.coroutines.flow.StateFlow
59 import kotlinx.coroutines.flow.distinctUntilChanged
60 import kotlinx.coroutines.flow.map
61 import kotlinx.coroutines.flow.onEach
62 import kotlinx.coroutines.flow.shareIn
63 import kotlinx.coroutines.flow.stateIn
64 
65 /**
66  * Provides data related to the connectivity state that needs to be shared across multiple different
67  * types of connectivity (wifi, mobile, ethernet, etc.)
68  */
69 interface ConnectivityRepository {
70     /** Observable for the current set of connectivity icons that should be force-hidden. */
71     val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>>
72 
73     /** Observable for which connection(s) are currently default. */
74     val defaultConnections: StateFlow<DefaultConnectionModel>
75 
76     /**
77      * Subscription ID of the [VcnTransportInfo] for the default connection.
78      *
79      * If the default network has a [VcnTransportInfo], then that transport info contains a subId of
80      * the VCN. When VCN is connected and default, this subId is what SystemUI will care about. In
81      * cases where telephony's activeDataSubscriptionId differs from this value, it is expected to
82      * eventually catch up and reflect what is represented here in the VcnTransportInfo.
83      */
84     val vcnSubId: StateFlow<Int?>
85 }
86 
87 @SuppressLint("MissingPermission")
88 @SysUISingleton
89 class ConnectivityRepositoryImpl
90 @Inject
91 constructor(
92     private val connectivityManager: ConnectivityManager,
93     private val connectivitySlots: ConnectivitySlots,
94     context: Context,
95     dumpManager: DumpManager,
96     logger: ConnectivityInputLogger,
97     @Application scope: CoroutineScope,
98     tunerService: TunerService,
99 ) : ConnectivityRepository, Dumpable {
100     init {
101         dumpManager.registerNormalDumpable("ConnectivityRepository", this)
102     }
103 
104     // The default set of hidden icons to use if we don't get any from [TunerService].
105     private val defaultHiddenIcons: Set<ConnectivitySlot> =
106         context.resources
107             .getStringArray(DEFAULT_HIDDEN_ICONS_RESOURCE)
108             .asList()
109             .toSlotSet(connectivitySlots)
110 
111     override val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>> =
<lambda>null112         conflatedCallbackFlow {
113                 val callback =
114                     object : TunerService.Tunable {
115                         override fun onTuningChanged(key: String, newHideList: String?) {
116                             if (key != HIDDEN_ICONS_TUNABLE_KEY) {
117                                 return
118                             }
119                             logger.logTuningChanged(newHideList)
120 
121                             val outputList =
122                                 newHideList?.split(",")?.toSlotSet(connectivitySlots)
123                                     ?: defaultHiddenIcons
124                             trySend(outputList)
125                         }
126                     }
127                 tunerService.addTunable(callback, HIDDEN_ICONS_TUNABLE_KEY)
128 
129                 awaitClose { tunerService.removeTunable(callback) }
130             }
131             .stateIn(
132                 scope,
133                 started = SharingStarted.WhileSubscribed(),
134                 initialValue = defaultHiddenIcons
135             )
136 
137     private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> =
<lambda>null138         conflatedCallbackFlow {
139                 val callback =
140                     object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
141                         override fun onLost(network: Network) {
142                             logger.logOnDefaultLost(network)
143                             trySend(null)
144                         }
145 
146                         override fun onCapabilitiesChanged(
147                             network: Network,
148                             networkCapabilities: NetworkCapabilities,
149                         ) {
150                             logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
151                             trySend(networkCapabilities)
152                         }
153                     }
154 
155                 connectivityManager.registerDefaultNetworkCallback(callback)
156 
157                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
158             }
159             .shareIn(scope, SharingStarted.WhileSubscribed())
160 
161     override val vcnSubId: StateFlow<Int?> =
162         defaultNetworkCapabilities
networkCapabilitiesnull163             .map { networkCapabilities ->
164                 networkCapabilities?.run {
165                     val subId =
166                         VcnUtils.getSubIdFromVcnCaps(connectivityManager, networkCapabilities)
167 
168                     // Never return an INVALID_SUBSCRIPTION_ID (-1)
169                     if (subId != INVALID_SUBSCRIPTION_ID) {
170                         subId
171                     } else {
172                         null
173                     }
174                 }
175             }
176             .distinctUntilChanged()
177             /* A note for logging: we use -2 here since -1 == INVALID_SUBSCRIPTION_ID */
<lambda>null178             .onEach { logger.logVcnSubscriptionId(it ?: -2) }
179             .stateIn(scope, SharingStarted.Eagerly, null)
180 
181     @SuppressLint("MissingPermission")
182     override val defaultConnections: StateFlow<DefaultConnectionModel> =
183         defaultNetworkCapabilities
networkCapabilitiesnull184             .map { networkCapabilities ->
185                 if (networkCapabilities == null) {
186                     // The system no longer has a default network, so everything is
187                     // non-default.
188                     DefaultConnectionModel(
189                         Wifi(isDefault = false),
190                         Mobile(isDefault = false),
191                         CarrierMerged(isDefault = false),
192                         Ethernet(isDefault = false),
193                         isValidated = false,
194                     )
195                 } else {
196                     val wifiInfo =
197                         networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
198 
199                     val isWifiDefault =
200                         networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
201                     val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
202                     val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
203                     val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
204 
205                     val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
206 
207                     DefaultConnectionModel(
208                         Wifi(isWifiDefault),
209                         Mobile(isMobileDefault),
210                         CarrierMerged(isCarrierMergedDefault),
211                         Ethernet(isEthernetDefault),
212                         isValidated,
213                     )
214                 }
215             }
216             .distinctUntilChanged()
<lambda>null217             .onEach { logger.logDefaultConnectionsChanged(it) }
218             .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel())
219 
dumpnull220     override fun dump(pw: PrintWriter, args: Array<out String>) {
221         pw.apply { println("defaultHiddenIcons=$defaultHiddenIcons") }
222     }
223 
224     companion object {
225         @VisibleForTesting
226         internal const val HIDDEN_ICONS_TUNABLE_KEY = StatusBarIconController.ICON_HIDE_LIST
227         @VisibleForTesting
228         @ArrayRes
229         internal val DEFAULT_HIDDEN_ICONS_RESOURCE = R.array.config_statusBarIconsToExclude
230 
231         /** Converts a list of string slot names to a set of [ConnectivitySlot] instances. */
Listnull232         private fun List<String>.toSlotSet(
233             connectivitySlots: ConnectivitySlots
234         ): Set<ConnectivitySlot> {
235             return this.filter { it.isNotBlank() }
236                 .mapNotNull { connectivitySlots.getSlotFromName(it) }
237                 .toSet()
238         }
239 
240         /**
241          * Returns a [WifiInfo] object from the capabilities if it has one, or null if there is no
242          * underlying wifi network.
243          *
244          * This will return a valid [WifiInfo] object if wifi is the main transport **or** wifi is
245          * an underlying transport. This is important for carrier merged networks, where the main
246          * transport info is *not* wifi, but the underlying transport info *is* wifi. We want to
247          * always use [WifiInfo] if it's available, so we need to check the underlying transport
248          * info.
249          */
getMainOrUnderlyingWifiInfonull250         fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(
251             connectivityManager: ConnectivityManager
252         ): WifiInfo? {
253             val mainWifiInfo = this.getMainWifiInfo(connectivityManager)
254             if (mainWifiInfo != null) {
255                 return mainWifiInfo
256             }
257             // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
258             // so skip the underlying network check if it's not CELLULAR.
259             if (
260                 !this.hasTransport(TRANSPORT_CELLULAR) &&
261                     !Flags.statusBarAlwaysCheckUnderlyingNetworks()
262             ) {
263                 return mainWifiInfo
264             }
265 
266             // Some connections, like VPN connections, may have underlying networks that are
267             // eventually traced to a wifi or carrier merged connection. So, check those underlying
268             // networks for possible wifi information as well. See b/225902574.
269             return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork ->
270                 connectivityManager
271                     .getNetworkCapabilities(underlyingNetwork)
272                     ?.getMainWifiInfo(connectivityManager)
273             }
274         }
275 
276         /**
277          * Checks the network capabilities for wifi info, but does *not* check the underlying
278          * networks. See [getMainOrUnderlyingWifiInfo].
279          */
getMainWifiInfonull280         private fun NetworkCapabilities.getMainWifiInfo(
281             connectivityManager: ConnectivityManager
282         ): WifiInfo? {
283             // Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
284             // virtual networks like VCN.
285             val canHaveWifiInfo =
286                 this.hasTransport(TRANSPORT_CELLULAR) || this.hasTransport(TRANSPORT_WIFI)
287             if (!canHaveWifiInfo) {
288                 return null
289             }
290 
291             return when (val currentTransportInfo = transportInfo) {
292                 // This VcnTransportInfo logic is copied from
293                 // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
294                 // re-used because it makes the logic here clearer, and because the method will be
295                 // removed once this pipeline is fully launched.
296                 is VcnTransportInfo -> VcnUtils.getWifiInfoFromVcnCaps(connectivityManager, this)
297                 is WifiInfo -> currentTransportInfo
298                 else -> null
299             }
300         }
301     }
302 }
303