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