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( 335 com.android.wifitrackerlib.R.string.wifi_connected_low_quality); 336 return; 337 } 338 } 339 340 ScoredNetwork scoredNetwork = 341 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo)); 342 statusLabel = scoredNetwork == null 343 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi); 344 } 345 346 @Nullable getMainOrUnderlyingWifiInfo( @ullable NetworkCapabilities networkCapabilities)347 private WifiInfo getMainOrUnderlyingWifiInfo( 348 @Nullable NetworkCapabilities networkCapabilities) { 349 if (networkCapabilities == null) { 350 return null; 351 } 352 353 WifiInfo mainWifiInfo = getMainWifiInfo(networkCapabilities); 354 if (mainWifiInfo != null) { 355 return mainWifiInfo; 356 } 357 358 // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI, 359 // so skip the underlying network check if it's not CELLULAR. 360 if (!networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { 361 return mainWifiInfo; 362 } 363 364 List<Network> underlyingNetworks = networkCapabilities.getUnderlyingNetworks(); 365 if (underlyingNetworks == null) { 366 return null; 367 } 368 369 // Some connections, like VPN connections, may have underlying networks that are 370 // eventually traced to a wifi or carrier merged connection. So, check those underlying 371 // networks for possible wifi information as well. See b/225902574. 372 for (Network underlyingNetwork : underlyingNetworks) { 373 NetworkCapabilities underlyingNetworkCapabilities = 374 mConnectivityManager.getNetworkCapabilities(underlyingNetwork); 375 WifiInfo underlyingWifiInfo = getMainWifiInfo(underlyingNetworkCapabilities); 376 if (underlyingWifiInfo != null) { 377 return underlyingWifiInfo; 378 } 379 } 380 381 return null; 382 } 383 384 @Nullable getMainWifiInfo(@ullable NetworkCapabilities networkCapabilities)385 private WifiInfo getMainWifiInfo(@Nullable NetworkCapabilities networkCapabilities) { 386 if (networkCapabilities == null) { 387 return null; 388 } 389 boolean canHaveWifiInfo = networkCapabilities.hasTransport(TRANSPORT_WIFI) 390 || networkCapabilities.hasTransport(TRANSPORT_CELLULAR); 391 if (!canHaveWifiInfo) { 392 return null; 393 } 394 395 TransportInfo transportInfo = networkCapabilities.getTransportInfo(); 396 if (transportInfo instanceof VcnTransportInfo) { 397 // This VcnTransportInfo logic is copied from 398 // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of 399 // re-used because it makes the logic here clearer. 400 return ((VcnTransportInfo) transportInfo).getWifiInfo(); 401 } else if (transportInfo instanceof WifiInfo) { 402 return (WifiInfo) transportInfo; 403 } else { 404 return null; 405 } 406 } 407 connectionIsWifi(NetworkCapabilities networkCapabilities)408 private boolean connectionIsWifi(NetworkCapabilities networkCapabilities) { 409 return connectionIsWifi( 410 networkCapabilities, 411 getMainOrUnderlyingWifiInfo(networkCapabilities)); 412 } 413 connectionIsWifi( @ullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo)414 private boolean connectionIsWifi( 415 @Nullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) { 416 if (networkCapabilities == null) { 417 return false; 418 } 419 return wifiInfo != null || networkCapabilities.hasTransport(TRANSPORT_WIFI); 420 } 421 422 /** Refresh the status label on Locale changed. */ refreshLocale()423 public void refreshLocale() { 424 updateStatusLabel(); 425 mMainThreadHandler.post(() -> postResults()); 426 } 427 getValidSsid(WifiInfo info)428 private String getValidSsid(WifiInfo info) { 429 String ssid = info.getSSID(); 430 if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) { 431 return ssid; 432 } 433 return null; 434 } 435 recordLastWifiNetwork(String log)436 private void recordLastWifiNetwork(String log) { 437 mHistory[mHistoryIndex] = log; 438 mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; 439 } 440 postResults()441 private void postResults() { 442 mCallback.run(); 443 } 444 445 /** Dump function. */ dump(PrintWriter pw)446 public void dump(PrintWriter pw) { 447 pw.println(" - WiFi Network History ------"); 448 int size = 0; 449 for (int i = 0; i < HISTORY_SIZE; i++) { 450 if (mHistory[i] != null) size++; 451 } 452 // Print out the previous states in ordered number. 453 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 454 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 455 pw.println(" Previous WiFiNetwork(" 456 + (mHistoryIndex + HISTORY_SIZE - i) + "): " 457 + mHistory[i & (HISTORY_SIZE - 1)]); 458 } 459 } 460 } 461