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.Looper; 34 import android.provider.Settings; 35 36 import com.android.settingslib.R; 37 import com.android.settingslib.Utils; 38 39 import java.io.PrintWriter; 40 import java.text.SimpleDateFormat; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Set; 44 45 /** 46 * Track status of Wi-Fi for the Sys UI. 47 */ 48 public class WifiStatusTracker { 49 private static final int HISTORY_SIZE = 32; 50 private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 51 private final Context mContext; 52 private final WifiNetworkScoreCache mWifiNetworkScoreCache; 53 private final WifiManager mWifiManager; 54 private final NetworkScoreManager mNetworkScoreManager; 55 private final ConnectivityManager mConnectivityManager; 56 private final Handler mHandler = new Handler(Looper.getMainLooper()); 57 private final Set<Integer> mNetworks = new HashSet<>(); 58 // Save the previous HISTORY_SIZE states for logging. 59 private final String[] mHistory = new String[HISTORY_SIZE]; 60 // Where to copy the next state into. 61 private int mHistoryIndex; 62 private final WifiNetworkScoreCache.CacheListener mCacheListener = 63 new WifiNetworkScoreCache.CacheListener(mHandler) { 64 @Override 65 public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) { 66 updateStatusLabel(); 67 mCallback.run(); 68 } 69 }; 70 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 71 .clearCapabilities() 72 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 73 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 74 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build(); 75 private final NetworkCallback mNetworkCallback = 76 new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { 77 // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable 78 // and onLinkPropertiesChanged. 79 @Override 80 public void onCapabilitiesChanged( 81 Network network, NetworkCapabilities networkCapabilities) { 82 boolean isVcnOverWifi = false; 83 boolean isWifi = false; 84 WifiInfo wifiInfo = null; 85 if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 86 wifiInfo = Utils.tryGetWifiInfoForVcn(networkCapabilities); 87 isVcnOverWifi = (wifiInfo != null); 88 } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { 89 wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo(); 90 isWifi = true; 91 } 92 // As long as it is a WiFi network, we will log it in the dumpsys for debugging. 93 if (isVcnOverWifi || isWifi) { 94 String log = new StringBuilder() 95 .append(SSDF.format(System.currentTimeMillis())).append(",") 96 .append("onCapabilitiesChanged: ") 97 .append("network=").append(network).append(",") 98 .append("networkCapabilities=").append(networkCapabilities) 99 .toString(); 100 recordLastWifiNetwork(log); 101 } 102 // Ignore the WiFi network if it doesn't contain any valid WifiInfo, or it is not the 103 // primary WiFi. 104 if (wifiInfo == null || !wifiInfo.isPrimary()) { 105 // Remove the network from the tracking list once it becomes non-primary. 106 if (mNetworks.contains(network.getNetId())) { 107 mNetworks.remove(network.getNetId()); 108 } 109 return; 110 } 111 if (!mNetworks.contains(network.getNetId())) { 112 mNetworks.add(network.getNetId()); 113 } 114 updateWifiInfo(wifiInfo); 115 updateStatusLabel(); 116 mCallback.run(); 117 } 118 119 @Override 120 public void onLost(Network network) { 121 String log = new StringBuilder() 122 .append(SSDF.format(System.currentTimeMillis())).append(",") 123 .append("onLost: ") 124 .append("network=").append(network) 125 .toString(); 126 recordLastWifiNetwork(log); 127 if (mNetworks.contains(network.getNetId())) { 128 mNetworks.remove(network.getNetId()); 129 updateWifiInfo(null); 130 updateStatusLabel(); 131 mCallback.run(); 132 } 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 mCallback.run(); 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 mCallback.run(); 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 mContext = context; 178 mWifiManager = wifiManager; 179 mWifiNetworkScoreCache = new WifiNetworkScoreCache(context); 180 mNetworkScoreManager = networkScoreManager; 181 mConnectivityManager = connectivityManager; 182 mCallback = callback; 183 } 184 setListening(boolean listening)185 public void setListening(boolean listening) { 186 if (listening) { 187 mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, 188 mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); 189 mWifiNetworkScoreCache.registerListener(mCacheListener); 190 mConnectivityManager.registerNetworkCallback( 191 mNetworkRequest, mNetworkCallback, mHandler); 192 mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); 193 } else { 194 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, 195 mWifiNetworkScoreCache); 196 mWifiNetworkScoreCache.unregisterListener(); 197 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 198 mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback); 199 } 200 } 201 202 /** 203 * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received. 204 * This replaces the dependency on the initial sticky broadcast. 205 */ fetchInitialState()206 public void fetchInitialState() { 207 if (mWifiManager == null) { 208 return; 209 } 210 updateWifiState(); 211 final NetworkInfo networkInfo = 212 mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 213 connected = networkInfo != null && networkInfo.isConnected(); 214 mWifiInfo = null; 215 ssid = null; 216 if (connected) { 217 mWifiInfo = mWifiManager.getConnectionInfo(); 218 if (mWifiInfo != null) { 219 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) { 220 ssid = mWifiInfo.getPasspointProviderFriendlyName(); 221 } else { 222 ssid = getValidSsid(mWifiInfo); 223 } 224 isCarrierMerged = mWifiInfo.isCarrierMerged(); 225 subId = mWifiInfo.getSubscriptionId(); 226 updateRssi(mWifiInfo.getRssi()); 227 maybeRequestNetworkScore(); 228 } 229 } 230 updateStatusLabel(); 231 } 232 handleBroadcast(Intent intent)233 public void handleBroadcast(Intent intent) { 234 if (mWifiManager == null) { 235 return; 236 } 237 String action = intent.getAction(); 238 if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 239 updateWifiState(); 240 } 241 } 242 updateWifiInfo(WifiInfo wifiInfo)243 private void updateWifiInfo(WifiInfo wifiInfo) { 244 updateWifiState(); 245 connected = wifiInfo != null; 246 mWifiInfo = wifiInfo; 247 ssid = null; 248 if (mWifiInfo != null) { 249 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) { 250 ssid = mWifiInfo.getPasspointProviderFriendlyName(); 251 } else { 252 ssid = getValidSsid(mWifiInfo); 253 } 254 isCarrierMerged = mWifiInfo.isCarrierMerged(); 255 subId = mWifiInfo.getSubscriptionId(); 256 updateRssi(mWifiInfo.getRssi()); 257 maybeRequestNetworkScore(); 258 } 259 } 260 updateWifiState()261 private void updateWifiState() { 262 state = mWifiManager.getWifiState(); 263 enabled = state == WifiManager.WIFI_STATE_ENABLED; 264 } 265 updateRssi(int newRssi)266 private void updateRssi(int newRssi) { 267 rssi = newRssi; 268 level = mWifiManager.calculateSignalLevel(rssi); 269 } 270 maybeRequestNetworkScore()271 private void maybeRequestNetworkScore() { 272 NetworkKey networkKey = NetworkKey.createFromWifiInfo(mWifiInfo); 273 if (mWifiNetworkScoreCache.getScoredNetwork(networkKey) == null) { 274 mNetworkScoreManager.requestScores(new NetworkKey[]{ networkKey }); 275 } 276 } 277 updateStatusLabel()278 private void updateStatusLabel() { 279 if (mWifiManager == null) { 280 return; 281 } 282 NetworkCapabilities networkCapabilities; 283 isDefaultNetwork = false; 284 if (mDefaultNetworkCapabilities != null) { 285 boolean isWifi = mDefaultNetworkCapabilities.hasTransport( 286 NetworkCapabilities.TRANSPORT_WIFI); 287 boolean isVcnOverWifi = mDefaultNetworkCapabilities.hasTransport( 288 NetworkCapabilities.TRANSPORT_CELLULAR) 289 && (Utils.tryGetWifiInfoForVcn(mDefaultNetworkCapabilities) != null); 290 if (isWifi || isVcnOverWifi) { 291 isDefaultNetwork = true; 292 } 293 } 294 if (isDefaultNetwork) { 295 // Wifi is connected and the default network. 296 networkCapabilities = mDefaultNetworkCapabilities; 297 } else { 298 networkCapabilities = mConnectivityManager.getNetworkCapabilities( 299 mWifiManager.getCurrentNetwork()); 300 } 301 isCaptivePortal = false; 302 if (networkCapabilities != null) { 303 if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) { 304 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required); 305 isCaptivePortal = true; 306 return; 307 } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) { 308 statusLabel = mContext.getString(R.string.wifi_limited_connection); 309 return; 310 } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { 311 final String mode = Settings.Global.getString(mContext.getContentResolver(), 312 Settings.Global.PRIVATE_DNS_MODE); 313 if (networkCapabilities.isPrivateDnsBroken()) { 314 statusLabel = mContext.getString(R.string.private_dns_broken); 315 } else { 316 statusLabel = mContext.getString(R.string.wifi_status_no_internet); 317 } 318 return; 319 } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null 320 && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { 321 statusLabel = mContext.getString(R.string.wifi_connected_low_quality); 322 return; 323 } 324 } 325 326 ScoredNetwork scoredNetwork = 327 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo)); 328 statusLabel = scoredNetwork == null 329 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi); 330 } 331 332 /** Refresh the status label on Locale changed. */ refreshLocale()333 public void refreshLocale() { 334 updateStatusLabel(); 335 mCallback.run(); 336 } 337 getValidSsid(WifiInfo info)338 private String getValidSsid(WifiInfo info) { 339 String ssid = info.getSSID(); 340 if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) { 341 return ssid; 342 } 343 return null; 344 } 345 recordLastWifiNetwork(String log)346 private void recordLastWifiNetwork(String log) { 347 mHistory[mHistoryIndex] = log; 348 mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; 349 } 350 351 /** Dump function. */ dump(PrintWriter pw)352 public void dump(PrintWriter pw) { 353 pw.println(" - WiFi Network History ------"); 354 int size = 0; 355 for (int i = 0; i < HISTORY_SIZE; i++) { 356 if (mHistory[i] != null) size++; 357 } 358 // Print out the previous states in ordered number. 359 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 360 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 361 pw.println(" Previous WiFiNetwork(" 362 + (mHistoryIndex + HISTORY_SIZE - i) + "): " 363 + mHistory[i & (HISTORY_SIZE - 1)]); 364 } 365 } 366 } 367