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