1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 5 * 6 * http://www.apache.org/licenses/LICENSE-2.0 7 * 8 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 9 */ 10 11 package com.android.settingslib.wifi; 12 13 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; 14 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; 15 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 16 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 17 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 18 19 import android.annotation.SuppressLint; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.ConnectivityManager; 23 import android.net.ConnectivityManager.NetworkCallback; 24 import android.net.Network; 25 import android.net.NetworkCapabilities; 26 import android.net.NetworkInfo; 27 import android.net.NetworkKey; 28 import android.net.NetworkRequest; 29 import android.net.NetworkScoreManager; 30 import android.net.ScoredNetwork; 31 import android.net.TransportInfo; 32 import android.net.vcn.VcnTransportInfo; 33 import android.net.vcn.VcnUtils; 34 import android.net.wifi.WifiInfo; 35 import android.net.wifi.WifiManager; 36 import android.net.wifi.WifiNetworkScoreCache; 37 import android.os.Handler; 38 import android.os.HandlerThread; 39 import android.os.Looper; 40 import android.provider.Settings; 41 42 import androidx.annotation.Nullable; 43 44 import com.android.settingslib.R; 45 46 import java.io.PrintWriter; 47 import java.text.SimpleDateFormat; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Set; 51 52 /** 53 * Track status of Wi-Fi for the Sys UI. 54 */ 55 @SuppressLint("MissingPermission") 56 public class WifiStatusTracker { 57 private static final int HISTORY_SIZE = 32; 58 private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 59 private final Context mContext; 60 private final WifiNetworkScoreCache mWifiNetworkScoreCache; 61 private final WifiManager mWifiManager; 62 private final NetworkScoreManager mNetworkScoreManager; 63 private final ConnectivityManager mConnectivityManager; 64 private final Handler mHandler; 65 private final Handler mMainThreadHandler; 66 private final Set<Integer> mNetworks = new HashSet<>(); 67 private int mPrimaryNetworkId; 68 // Save the previous HISTORY_SIZE states for logging. 69 private final String[] mHistory = new String[HISTORY_SIZE]; 70 // Where to copy the next state into. 71 private int mHistoryIndex; 72 private final WifiNetworkScoreCache.CacheListener mCacheListener; 73 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 74 .clearCapabilities() 75 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 76 .addTransportType(TRANSPORT_WIFI) 77 .addTransportType(TRANSPORT_CELLULAR) 78 .build(); 79 private final NetworkCallback mNetworkCallback = 80 new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { 81 // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable 82 // and onLinkPropertiesChanged. 83 @Override 84 public void onCapabilitiesChanged( 85 Network network, NetworkCapabilities networkCapabilities) { 86 WifiInfo wifiInfo = getMainOrUnderlyingWifiInfo(networkCapabilities); 87 boolean isWifi = connectionIsWifi(networkCapabilities, wifiInfo); 88 // As long as it is a WiFi network, we will log it in the dumpsys for debugging. 89 if (isWifi) { 90 String log = new StringBuilder() 91 .append(SSDF.format(System.currentTimeMillis())).append(",") 92 .append("onCapabilitiesChanged: ") 93 .append("network=").append(network).append(",") 94 .append("networkCapabilities=").append(networkCapabilities) 95 .toString(); 96 recordLastWifiNetwork(log); 97 } 98 // Ignore the WiFi network if it doesn't contain any valid WifiInfo, or it is not the 99 // primary WiFi. 100 if (wifiInfo == null || !wifiInfo.isPrimary()) { 101 // Remove the network from the tracking list once it becomes non-primary. 102 if (mNetworks.contains(network.getNetId())) { 103 mNetworks.remove(network.getNetId()); 104 } 105 return; 106 } 107 if (!mNetworks.contains(network.getNetId())) { 108 mNetworks.add(network.getNetId()); 109 } 110 mPrimaryNetworkId = network.getNetId(); 111 updateWifiInfo(wifiInfo); 112 updateStatusLabel(); 113 mMainThreadHandler.post(() -> postResults()); 114 } 115 116 @Override 117 public void onLost(Network network) { 118 String log = new StringBuilder() 119 .append(SSDF.format(System.currentTimeMillis())).append(",") 120 .append("onLost: ") 121 .append("network=").append(network) 122 .toString(); 123 recordLastWifiNetwork(log); 124 if (mNetworks.contains(network.getNetId())) { 125 mNetworks.remove(network.getNetId()); 126 } 127 if (network.getNetId() != mPrimaryNetworkId) { 128 return; 129 } 130 updateWifiInfo(null); 131 updateStatusLabel(); 132 mMainThreadHandler.post(() -> postResults()); 133 } 134 }; 135 private final NetworkCallback mDefaultNetworkCallback = 136 new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { 137 @Override 138 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 139 // network is now the default network, and its capabilities are nc. 140 // This method will always be called immediately after the network becomes the 141 // default, in addition to any time the capabilities change while the network is 142 // the default. 143 mDefaultNetwork = network; 144 mDefaultNetworkCapabilities = nc; 145 updateStatusLabel(); 146 mMainThreadHandler.post(() -> postResults()); 147 } 148 @Override 149 public void onLost(Network network) { 150 // The system no longer has a default network. 151 mDefaultNetwork = null; 152 mDefaultNetworkCapabilities = null; 153 updateStatusLabel(); 154 mMainThreadHandler.post(() -> postResults()); 155 } 156 }; 157 private Network mDefaultNetwork = null; 158 private NetworkCapabilities mDefaultNetworkCapabilities = null; 159 private final Runnable mCallback; 160 161 private WifiInfo mWifiInfo; 162 public boolean enabled; 163 public boolean isCaptivePortal; 164 public boolean isDefaultNetwork; 165 public boolean isCarrierMerged; 166 public int subId; 167 public int state; 168 public boolean connected; 169 public String ssid; 170 public int rssi; 171 public int level; 172 public String statusLabel; 173 WifiStatusTracker(Context context, WifiManager wifiManager, NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, Runnable callback)174 public WifiStatusTracker(Context context, WifiManager wifiManager, 175 NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, 176 Runnable callback) { 177 this(context, wifiManager, networkScoreManager, connectivityManager, callback, null, null); 178 } 179 WifiStatusTracker(Context context, WifiManager wifiManager, NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, Runnable callback, Handler foregroundHandler, Handler backgroundHandler)180 public WifiStatusTracker(Context context, WifiManager wifiManager, 181 NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, 182 Runnable callback, Handler foregroundHandler, Handler backgroundHandler) { 183 mContext = context; 184 mWifiManager = wifiManager; 185 mWifiNetworkScoreCache = new WifiNetworkScoreCache(context); 186 mNetworkScoreManager = networkScoreManager; 187 mConnectivityManager = connectivityManager; 188 mCallback = callback; 189 if (backgroundHandler == null) { 190 HandlerThread handlerThread = new HandlerThread("WifiStatusTrackerHandler"); 191 handlerThread.start(); 192 mHandler = new Handler(handlerThread.getLooper()); 193 } else { 194 mHandler = backgroundHandler; 195 } 196 mMainThreadHandler = foregroundHandler == null 197 ? new Handler(Looper.getMainLooper()) : foregroundHandler; 198 mCacheListener = 199 new WifiNetworkScoreCache.CacheListener(mHandler) { 200 @Override 201 public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) { 202 updateStatusLabel(); 203 mMainThreadHandler.post(() -> postResults()); 204 } 205 }; 206 } 207 setListening(boolean listening)208 public void setListening(boolean listening) { 209 if (listening) { 210 mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, 211 mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); 212 mWifiNetworkScoreCache.registerListener(mCacheListener); 213 mConnectivityManager.registerNetworkCallback( 214 mNetworkRequest, mNetworkCallback, mHandler); 215 mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); 216 } else { 217 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, 218 mWifiNetworkScoreCache); 219 mWifiNetworkScoreCache.unregisterListener(); 220 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 221 mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback); 222 } 223 } 224 225 /** 226 * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received. 227 * This replaces the dependency on the initial sticky broadcast. 228 */ fetchInitialState()229 public void fetchInitialState() { 230 if (mWifiManager == null) { 231 return; 232 } 233 updateWifiState(); 234 final NetworkInfo networkInfo = 235 mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 236 connected = networkInfo != null && networkInfo.isConnected(); 237 mWifiInfo = null; 238 ssid = null; 239 if (connected) { 240 mWifiInfo = mWifiManager.getConnectionInfo(); 241 if (mWifiInfo != null) { 242 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) { 243 ssid = mWifiInfo.getPasspointProviderFriendlyName(); 244 } else { 245 ssid = getValidSsid(mWifiInfo); 246 } 247 isCarrierMerged = mWifiInfo.isCarrierMerged(); 248 subId = mWifiInfo.getSubscriptionId(); 249 updateRssi(mWifiInfo.getRssi()); 250 maybeRequestNetworkScore(); 251 } 252 } 253 updateStatusLabel(); 254 } 255 handleBroadcast(Intent intent)256 public void handleBroadcast(Intent intent) { 257 if (mWifiManager == null) { 258 return; 259 } 260 String action = intent.getAction(); 261 if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 262 updateWifiState(); 263 } 264 } 265 updateWifiInfo(WifiInfo wifiInfo)266 private void updateWifiInfo(WifiInfo wifiInfo) { 267 updateWifiState(); 268 connected = wifiInfo != null; 269 mWifiInfo = wifiInfo; 270 ssid = null; 271 if (mWifiInfo != null) { 272 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) { 273 ssid = mWifiInfo.getPasspointProviderFriendlyName(); 274 } else { 275 ssid = getValidSsid(mWifiInfo); 276 } 277 isCarrierMerged = mWifiInfo.isCarrierMerged(); 278 subId = mWifiInfo.getSubscriptionId(); 279 updateRssi(mWifiInfo.getRssi()); 280 maybeRequestNetworkScore(); 281 } 282 } 283 updateWifiState()284 private void updateWifiState() { 285 state = mWifiManager.getWifiState(); 286 enabled = state == WifiManager.WIFI_STATE_ENABLED; 287 } 288 updateRssi(int newRssi)289 private void updateRssi(int newRssi) { 290 rssi = newRssi; 291 level = mWifiManager.calculateSignalLevel(rssi); 292 } 293 maybeRequestNetworkScore()294 private void maybeRequestNetworkScore() { 295 NetworkKey networkKey = NetworkKey.createFromWifiInfo(mWifiInfo); 296 if (mWifiNetworkScoreCache.getScoredNetwork(networkKey) == null) { 297 mNetworkScoreManager.requestScores(new NetworkKey[]{ networkKey }); 298 } 299 } 300 updateStatusLabel()301 private void updateStatusLabel() { 302 if (mWifiManager == null) { 303 return; 304 } 305 NetworkCapabilities networkCapabilities; 306 isDefaultNetwork = mDefaultNetworkCapabilities != null 307 && connectionIsWifi(mDefaultNetworkCapabilities); 308 if (isDefaultNetwork) { 309 // Wifi is connected and the default network. 310 networkCapabilities = mDefaultNetworkCapabilities; 311 } else { 312 networkCapabilities = mConnectivityManager.getNetworkCapabilities( 313 mWifiManager.getCurrentNetwork()); 314 } 315 isCaptivePortal = false; 316 if (networkCapabilities != null) { 317 if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) { 318 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required); 319 isCaptivePortal = true; 320 return; 321 } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) { 322 statusLabel = mContext.getString(R.string.wifi_limited_connection); 323 return; 324 } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { 325 final String mode = Settings.Global.getString(mContext.getContentResolver(), 326 Settings.Global.PRIVATE_DNS_MODE); 327 if (networkCapabilities.isPrivateDnsBroken()) { 328 statusLabel = mContext.getString(R.string.private_dns_broken); 329 } else { 330 statusLabel = mContext.getString(R.string.wifi_status_no_internet); 331 } 332 return; 333 } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null 334 && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { 335 statusLabel = mContext.getString( 336 com.android.wifitrackerlib.R.string.wifi_connected_low_quality); 337 return; 338 } 339 } 340 341 ScoredNetwork scoredNetwork = 342 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo)); 343 statusLabel = scoredNetwork == null 344 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi); 345 } 346 347 @Nullable getMainOrUnderlyingWifiInfo( @ullable NetworkCapabilities networkCapabilities)348 private WifiInfo getMainOrUnderlyingWifiInfo( 349 @Nullable NetworkCapabilities networkCapabilities) { 350 if (networkCapabilities == null) { 351 return null; 352 } 353 354 WifiInfo mainWifiInfo = getMainWifiInfo(networkCapabilities); 355 if (mainWifiInfo != null) { 356 return mainWifiInfo; 357 } 358 359 // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI, 360 // so skip the underlying network check if it's not CELLULAR. 361 if (!networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { 362 return mainWifiInfo; 363 } 364 365 List<Network> underlyingNetworks = networkCapabilities.getUnderlyingNetworks(); 366 if (underlyingNetworks == null) { 367 return null; 368 } 369 370 // Some connections, like VPN connections, may have underlying networks that are 371 // eventually traced to a wifi or carrier merged connection. So, check those underlying 372 // networks for possible wifi information as well. See b/225902574. 373 for (Network underlyingNetwork : underlyingNetworks) { 374 NetworkCapabilities underlyingNetworkCapabilities = 375 mConnectivityManager.getNetworkCapabilities(underlyingNetwork); 376 WifiInfo underlyingWifiInfo = getMainWifiInfo(underlyingNetworkCapabilities); 377 if (underlyingWifiInfo != null) { 378 return underlyingWifiInfo; 379 } 380 } 381 382 return null; 383 } 384 385 @Nullable getMainWifiInfo(@ullable NetworkCapabilities networkCapabilities)386 private WifiInfo getMainWifiInfo(@Nullable NetworkCapabilities networkCapabilities) { 387 if (networkCapabilities == null) { 388 return null; 389 } 390 boolean canHaveWifiInfo = networkCapabilities.hasTransport(TRANSPORT_WIFI) 391 || networkCapabilities.hasTransport(TRANSPORT_CELLULAR); 392 if (!canHaveWifiInfo) { 393 return null; 394 } 395 396 TransportInfo transportInfo = networkCapabilities.getTransportInfo(); 397 if (transportInfo instanceof VcnTransportInfo) { 398 return VcnUtils.getWifiInfoFromVcnCaps(mConnectivityManager, networkCapabilities); 399 } else if (transportInfo instanceof WifiInfo) { 400 return (WifiInfo) transportInfo; 401 } else { 402 return null; 403 } 404 } 405 connectionIsWifi(NetworkCapabilities networkCapabilities)406 private boolean connectionIsWifi(NetworkCapabilities networkCapabilities) { 407 return connectionIsWifi( 408 networkCapabilities, 409 getMainOrUnderlyingWifiInfo(networkCapabilities)); 410 } 411 connectionIsWifi( @ullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo)412 private boolean connectionIsWifi( 413 @Nullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) { 414 if (networkCapabilities == null) { 415 return false; 416 } 417 return wifiInfo != null || networkCapabilities.hasTransport(TRANSPORT_WIFI); 418 } 419 420 /** Refresh the status label on Locale changed. */ refreshLocale()421 public void refreshLocale() { 422 updateStatusLabel(); 423 mMainThreadHandler.post(() -> postResults()); 424 } 425 getValidSsid(WifiInfo info)426 private String getValidSsid(WifiInfo info) { 427 String ssid = info.getSSID(); 428 if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) { 429 return ssid; 430 } 431 return null; 432 } 433 recordLastWifiNetwork(String log)434 private void recordLastWifiNetwork(String log) { 435 mHistory[mHistoryIndex] = log; 436 mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; 437 } 438 postResults()439 private void postResults() { 440 mCallback.run(); 441 } 442 443 /** Dump function. */ dump(PrintWriter pw)444 public void dump(PrintWriter pw) { 445 pw.println(" - WiFi Network History ------"); 446 int size = 0; 447 for (int i = 0; i < HISTORY_SIZE; i++) { 448 if (mHistory[i] != null) size++; 449 } 450 // Print out the previous states in ordered number. 451 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 452 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 453 pw.println(" Previous WiFiNetwork(" 454 + (mHistoryIndex + HISTORY_SIZE - i) + "): " 455 + mHistory[i & (HISTORY_SIZE - 1)]); 456 } 457 } 458 } 459