• 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.wifi.data.repository.prod
18 
19 import android.annotation.SuppressLint
20 import android.content.IntentFilter
21 import android.net.ConnectivityManager
22 import android.net.Network
23 import android.net.NetworkCapabilities
24 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
25 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
26 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
27 import android.net.NetworkCapabilities.TRANSPORT_WIFI
28 import android.net.NetworkRequest
29 import android.net.wifi.WifiInfo
30 import android.net.wifi.WifiManager
31 import android.net.wifi.WifiManager.TrafficStateCallback
32 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
33 import com.android.settingslib.Utils
34 import com.android.systemui.broadcast.BroadcastDispatcher
35 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.dagger.qualifiers.Application
38 import com.android.systemui.dagger.qualifiers.Main
39 import com.android.systemui.log.table.TableLogBuffer
40 import com.android.systemui.log.table.logDiffsForTable
41 import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
42 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
43 import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
44 import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
45 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
46 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
47 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
48 import java.util.concurrent.Executor
49 import javax.inject.Inject
50 import kotlinx.coroutines.CoroutineScope
51 import kotlinx.coroutines.ExperimentalCoroutinesApi
52 import kotlinx.coroutines.channels.awaitClose
53 import kotlinx.coroutines.flow.Flow
54 import kotlinx.coroutines.flow.MutableSharedFlow
55 import kotlinx.coroutines.flow.SharingStarted
56 import kotlinx.coroutines.flow.StateFlow
57 import kotlinx.coroutines.flow.distinctUntilChanged
58 import kotlinx.coroutines.flow.mapLatest
59 import kotlinx.coroutines.flow.merge
60 import kotlinx.coroutines.flow.onEach
61 import kotlinx.coroutines.flow.stateIn
62 
63 /** Real implementation of [WifiRepository]. */
64 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
65 @OptIn(ExperimentalCoroutinesApi::class)
66 @SysUISingleton
67 @SuppressLint("MissingPermission")
68 class WifiRepositoryImpl
69 @Inject
70 constructor(
71     broadcastDispatcher: BroadcastDispatcher,
72     connectivityManager: ConnectivityManager,
73     logger: WifiInputLogger,
74     @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
75     @Main mainExecutor: Executor,
76     @Application scope: CoroutineScope,
77     wifiManager: WifiManager,
78 ) : RealWifiRepository {
79 
80     private val wifiStateChangeEvents: Flow<Unit> =
81         broadcastDispatcher
82             .broadcastFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
83             .onEach { logger.logIntent("WIFI_STATE_CHANGED_ACTION") }
84 
85     private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
86         MutableSharedFlow(extraBufferCapacity = 1)
87 
88     // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
89     // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
90     // have changed.
91     override val isWifiEnabled: StateFlow<Boolean> =
92         merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
93             .mapLatest { wifiManager.isWifiEnabled }
94             .distinctUntilChanged()
95             .logDiffsForTable(
96                 wifiTableLogBuffer,
97                 columnPrefix = "",
98                 columnName = "isEnabled",
99                 initialValue = wifiManager.isWifiEnabled,
100             )
101             .stateIn(
102                 scope = scope,
103                 started = SharingStarted.WhileSubscribed(),
104                 initialValue = wifiManager.isWifiEnabled,
105             )
106 
107     override val isWifiDefault: StateFlow<Boolean> =
108         conflatedCallbackFlow {
109                 // Note: This callback doesn't do any logging because we already log every network
110                 // change in the [wifiNetwork] callback.
111                 val callback =
112                     object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
113                         override fun onCapabilitiesChanged(
114                             network: Network,
115                             networkCapabilities: NetworkCapabilities
116                         ) {
117                             logger.logOnCapabilitiesChanged(
118                                 network,
119                                 networkCapabilities,
120                                 isDefaultNetworkCallback = true,
121                             )
122 
123                             // This method will always be called immediately after the network
124                             // becomes the default, in addition to any time the capabilities change
125                             // while the network is the default.
126                             // If this network is a wifi network, then wifi is the default network.
127                             trySend(isWifiNetwork(networkCapabilities))
128                         }
129 
130                         override fun onLost(network: Network) {
131                             logger.logOnLost(network, isDefaultNetworkCallback = true)
132                             // The system no longer has a default network, so wifi is definitely not
133                             // default.
134                             trySend(false)
135                         }
136                     }
137 
138                 connectivityManager.registerDefaultNetworkCallback(callback)
139                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
140             }
141             .distinctUntilChanged()
142             .logDiffsForTable(
143                 wifiTableLogBuffer,
144                 columnPrefix = "",
145                 columnName = "isDefault",
146                 initialValue = false,
147             )
148             .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
149 
150     override val wifiNetwork: StateFlow<WifiNetworkModel> =
151         conflatedCallbackFlow {
152                 var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
153 
154                 val callback =
155                     object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
156                         override fun onCapabilitiesChanged(
157                             network: Network,
158                             networkCapabilities: NetworkCapabilities
159                         ) {
160                             logger.logOnCapabilitiesChanged(
161                                 network,
162                                 networkCapabilities,
163                                 isDefaultNetworkCallback = false,
164                             )
165 
166                             wifiNetworkChangeEvents.tryEmit(Unit)
167 
168                             val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
169                             if (wifiInfo?.isPrimary == true) {
170                                 val wifiNetworkModel =
171                                     createWifiNetworkModel(
172                                         wifiInfo,
173                                         network,
174                                         networkCapabilities,
175                                         wifiManager,
176                                     )
177                                 currentWifi = wifiNetworkModel
178                                 trySend(wifiNetworkModel)
179                             }
180                         }
181 
182                         override fun onLost(network: Network) {
183                             logger.logOnLost(network, isDefaultNetworkCallback = false)
184 
185                             wifiNetworkChangeEvents.tryEmit(Unit)
186 
187                             val wifi = currentWifi
188                             if (
189                                 wifi is WifiNetworkModel.Active &&
190                                     wifi.networkId == network.getNetId()
191                             ) {
192                                 val newNetworkModel = WifiNetworkModel.Inactive
193                                 currentWifi = newNetworkModel
194                                 trySend(newNetworkModel)
195                             }
196                         }
197                     }
198 
199                 connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
200 
201                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
202             }
203             .distinctUntilChanged()
204             .logDiffsForTable(
205                 wifiTableLogBuffer,
206                 columnPrefix = "",
207                 initialValue = WIFI_NETWORK_DEFAULT,
208             )
209             // There will be multiple wifi icons in different places that will frequently
210             // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures
211             // that new subscribes will get the latest value immediately upon subscription.
212             // Otherwise, the views could show stale data. See b/244173280.
213             .stateIn(
214                 scope,
215                 started = SharingStarted.WhileSubscribed(),
216                 initialValue = WIFI_NETWORK_DEFAULT,
217             )
218 
219     override val wifiActivity: StateFlow<DataActivityModel> =
220         conflatedCallbackFlow {
221                 val callback = TrafficStateCallback { state ->
222                     logger.logActivity(prettyPrintActivity(state))
223                     trySend(state.toWifiDataActivityModel())
224                 }
225                 wifiManager.registerTrafficStateCallback(mainExecutor, callback)
226                 awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
227             }
228             .logDiffsForTable(
229                 wifiTableLogBuffer,
230                 columnPrefix = ACTIVITY_PREFIX,
231                 initialValue = ACTIVITY_DEFAULT,
232             )
233             .stateIn(
234                 scope,
235                 started = SharingStarted.WhileSubscribed(),
236                 initialValue = ACTIVITY_DEFAULT,
237             )
238 
239     companion object {
240         private const val ACTIVITY_PREFIX = "wifiActivity"
241 
242         val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
243         // Start out with no known wifi network.
244         // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
245         // initial fetch to get a starting wifi network. But, it uses a deprecated API
246         // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
247         // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
248         // NetworkCallback inside [wifiNetwork] for our wifi network information.
249         val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
250 
251         private fun networkCapabilitiesToWifiInfo(
252             networkCapabilities: NetworkCapabilities
253         ): WifiInfo? {
254             return when {
255                 networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
256                     // Sometimes, cellular networks can act as wifi networks (known as VCN --
257                     // virtual carrier network). So, see if this cellular network has wifi info.
258                     Utils.tryGetWifiInfoForVcn(networkCapabilities)
259                 networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
260                     if (networkCapabilities.transportInfo is WifiInfo) {
261                         networkCapabilities.transportInfo as WifiInfo
262                     } else {
263                         null
264                     }
265                 else -> null
266             }
267         }
268 
269         /** True if these capabilities represent a wifi network. */
270         private fun isWifiNetwork(networkCapabilities: NetworkCapabilities): Boolean {
271             return when {
272                 networkCapabilities.hasTransport(TRANSPORT_WIFI) -> true
273                 networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
274                     Utils.tryGetWifiInfoForVcn(networkCapabilities) != null
275                 else -> false
276             }
277         }
278 
279         private fun createWifiNetworkModel(
280             wifiInfo: WifiInfo,
281             network: Network,
282             networkCapabilities: NetworkCapabilities,
283             wifiManager: WifiManager,
284         ): WifiNetworkModel {
285             return if (wifiInfo.isCarrierMerged) {
286                 if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) {
287                     WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
288                 } else {
289                     WifiNetworkModel.CarrierMerged(
290                         networkId = network.getNetId(),
291                         subscriptionId = wifiInfo.subscriptionId,
292                         level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
293                         // The WiFi signal level returned by WifiManager#calculateSignalLevel start
294                         // from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level
295                         // buckets count.
296                         numberOfLevels = wifiManager.maxSignalLevel + 1,
297                     )
298                 }
299             } else {
300                 WifiNetworkModel.Active(
301                     network.getNetId(),
302                     isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
303                     level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
304                     wifiInfo.ssid,
305                     wifiInfo.isPasspointAp,
306                     wifiInfo.isOsuAp,
307                     wifiInfo.passpointProviderFriendlyName
308                 )
309             }
310         }
311 
312         private fun prettyPrintActivity(activity: Int): String {
313             return when (activity) {
314                 TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
315                 TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
316                 TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
317                 TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
318                 else -> "INVALID"
319             }
320         }
321 
322         private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
323             NetworkRequest.Builder()
324                 .clearCapabilities()
325                 .addCapability(NET_CAPABILITY_NOT_VPN)
326                 .addTransportType(TRANSPORT_WIFI)
327                 .addTransportType(TRANSPORT_CELLULAR)
328                 .build()
329 
330         private const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
331             "Wifi network was carrier merged but had invalid sub ID"
332     }
333 
334     @SysUISingleton
335     class Factory
336     @Inject
337     constructor(
338         private val broadcastDispatcher: BroadcastDispatcher,
339         private val connectivityManager: ConnectivityManager,
340         private val logger: WifiInputLogger,
341         @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
342         @Main private val mainExecutor: Executor,
343         @Application private val scope: CoroutineScope,
344     ) {
345         fun create(wifiManager: WifiManager): WifiRepositoryImpl {
346             return WifiRepositoryImpl(
347                 broadcastDispatcher,
348                 connectivityManager,
349                 logger,
350                 wifiTableLogBuffer,
351                 mainExecutor,
352                 scope,
353                 wifiManager,
354             )
355         }
356     }
357 }
358