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 18 import android.content.Context; 19 import android.content.Intent; 20 import android.net.ConnectivityManager; 21 import android.net.ConnectivityManager.NetworkCallback; 22 import android.net.Network; 23 import android.net.NetworkCapabilities; 24 import android.net.NetworkInfo; 25 import android.net.NetworkKey; 26 import android.net.NetworkRequest; 27 import android.net.NetworkScoreManager; 28 import android.net.ScoredNetwork; 29 import android.net.wifi.WifiInfo; 30 import android.net.wifi.WifiManager; 31 import android.net.wifi.WifiNetworkScoreCache; 32 import android.os.Handler; 33 import android.os.HandlerThread; 34 import android.os.Looper; 35 import android.provider.Settings; 36 37 import com.android.settingslib.R; 38 import com.android.settingslib.Utils; 39 40 import java.io.PrintWriter; 41 import java.text.SimpleDateFormat; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Set; 45 46 /** 47 * Track status of Wi-Fi for the Sys UI. 48 */ 49 public class WifiStatusTracker { 50 private static final int HISTORY_SIZE = 32; 51 private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 52 private final Context mContext; 53 private final WifiNetworkScoreCache mWifiNetworkScoreCache; 54 private final WifiManager mWifiManager; 55 private final NetworkScoreManager mNetworkScoreManager; 56 private final ConnectivityManager mConnectivityManager; 57 private final Handler mHandler; 58 private final Handler mMainThreadHandler; 59 private final Set<Integer> mNetworks = new HashSet<>(); 60 private int mPrimaryNetworkId; 61 // Save the previous HISTORY_SIZE states for logging. 62 private final String[] mHistory = new String[HISTORY_SIZE]; 63 // Where to copy the next state into. 64 private int mHistoryIndex; 65 private final WifiNetworkScoreCache.CacheListener mCacheListener; 66 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 67 .clearCapabilities() 68 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 69 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 70 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build(); 71 private final NetworkCallback mNetworkCallback = 72 new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { 73 // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable 74 // and onLinkPropertiesChanged. 75 @Override 76 public void onCapabilitiesChanged( 77 Network network, NetworkCapabilities networkCapabilities) { 78 boolean isVcnOverWifi = false; 79 boolean isWifi = false; 80 WifiInfo wifiInfo = null; 81 if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 82 wifiInfo = Utils.tryGetWifiInfoForVcn(networkCapabilities); 83 isVcnOverWifi = (wifiInfo != null); 84 } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { 85 wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo(); 86 isWifi = true; 87 } 88 // As long as it is a WiFi network, we will log it in the dumpsys for debugging. 89 if (isVcnOverWifi || 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 = false; 307 if (mDefaultNetworkCapabilities != null) { 308 boolean isWifi = mDefaultNetworkCapabilities.hasTransport( 309 NetworkCapabilities.TRANSPORT_WIFI); 310 boolean isVcnOverWifi = mDefaultNetworkCapabilities.hasTransport( 311 NetworkCapabilities.TRANSPORT_CELLULAR) 312 && (Utils.tryGetWifiInfoForVcn(mDefaultNetworkCapabilities) != null); 313 if (isWifi || isVcnOverWifi) { 314 isDefaultNetwork = true; 315 } 316 } 317 if (isDefaultNetwork) { 318 // Wifi is connected and the default network. 319 networkCapabilities = mDefaultNetworkCapabilities; 320 } else { 321 networkCapabilities = mConnectivityManager.getNetworkCapabilities( 322 mWifiManager.getCurrentNetwork()); 323 } 324 isCaptivePortal = false; 325 if (networkCapabilities != null) { 326 if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) { 327 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required); 328 isCaptivePortal = true; 329 return; 330 } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) { 331 statusLabel = mContext.getString(R.string.wifi_limited_connection); 332 return; 333 } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { 334 final String mode = Settings.Global.getString(mContext.getContentResolver(), 335 Settings.Global.PRIVATE_DNS_MODE); 336 if (networkCapabilities.isPrivateDnsBroken()) { 337 statusLabel = mContext.getString(R.string.private_dns_broken); 338 } else { 339 statusLabel = mContext.getString(R.string.wifi_status_no_internet); 340 } 341 return; 342 } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null 343 && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { 344 statusLabel = mContext.getString(R.string.wifi_connected_low_quality); 345 return; 346 } 347 } 348 349 ScoredNetwork scoredNetwork = 350 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo)); 351 statusLabel = scoredNetwork == null 352 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi); 353 } 354 355 /** Refresh the status label on Locale changed. */ refreshLocale()356 public void refreshLocale() { 357 updateStatusLabel(); 358 mMainThreadHandler.post(() -> postResults()); 359 } 360 getValidSsid(WifiInfo info)361 private String getValidSsid(WifiInfo info) { 362 String ssid = info.getSSID(); 363 if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) { 364 return ssid; 365 } 366 return null; 367 } 368 recordLastWifiNetwork(String log)369 private void recordLastWifiNetwork(String log) { 370 mHistory[mHistoryIndex] = log; 371 mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; 372 } 373 postResults()374 private void postResults() { 375 mCallback.run(); 376 } 377 378 /** Dump function. */ dump(PrintWriter pw)379 public void dump(PrintWriter pw) { 380 pw.println(" - WiFi Network History ------"); 381 int size = 0; 382 for (int i = 0; i < HISTORY_SIZE; i++) { 383 if (mHistory[i] != null) size++; 384 } 385 // Print out the previous states in ordered number. 386 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 387 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 388 pw.println(" Previous WiFiNetwork(" 389 + (mHistoryIndex + HISTORY_SIZE - i) + "): " 390 + mHistory[i & (HISTORY_SIZE - 1)]); 391 } 392 } 393 } 394