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 private final boolean mSupportMergedUi; 161 162 private WifiInfo mWifiInfo; 163 public boolean enabled; 164 public boolean isCaptivePortal; 165 public boolean isDefaultNetwork; 166 public boolean isCarrierMerged; 167 public int subId; 168 public int state; 169 public boolean connected; 170 public String ssid; 171 public int rssi; 172 public int level; 173 public String statusLabel; 174 WifiStatusTracker(Context context, WifiManager wifiManager, NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, Runnable callback)175 public WifiStatusTracker(Context context, WifiManager wifiManager, 176 NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, 177 Runnable callback) { 178 mContext = context; 179 mWifiManager = wifiManager; 180 mWifiNetworkScoreCache = new WifiNetworkScoreCache(context); 181 mNetworkScoreManager = networkScoreManager; 182 mConnectivityManager = connectivityManager; 183 mCallback = callback; 184 mSupportMergedUi = false; 185 } 186 setListening(boolean listening)187 public void setListening(boolean listening) { 188 if (listening) { 189 mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, 190 mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK); 191 mWifiNetworkScoreCache.registerListener(mCacheListener); 192 mConnectivityManager.registerNetworkCallback( 193 mNetworkRequest, mNetworkCallback, mHandler); 194 mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); 195 } else { 196 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, 197 mWifiNetworkScoreCache); 198 mWifiNetworkScoreCache.unregisterListener(); 199 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 200 mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback); 201 } 202 } 203 204 /** 205 * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received. 206 * This replaces the dependency on the initial sticky broadcast. 207 */ fetchInitialState()208 public void fetchInitialState() { 209 if (mWifiManager == null) { 210 return; 211 } 212 updateWifiState(); 213 final NetworkInfo networkInfo = 214 mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 215 connected = networkInfo != null && networkInfo.isConnected(); 216 mWifiInfo = null; 217 ssid = null; 218 if (connected) { 219 mWifiInfo = mWifiManager.getConnectionInfo(); 220 if (mWifiInfo != null) { 221 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) { 222 ssid = mWifiInfo.getPasspointProviderFriendlyName(); 223 } else { 224 ssid = getValidSsid(mWifiInfo); 225 } 226 if (mSupportMergedUi) { 227 isCarrierMerged = mWifiInfo.isCarrierMerged(); 228 subId = mWifiInfo.getSubscriptionId(); 229 } 230 updateRssi(mWifiInfo.getRssi()); 231 maybeRequestNetworkScore(); 232 } 233 } 234 updateStatusLabel(); 235 } 236 handleBroadcast(Intent intent)237 public void handleBroadcast(Intent intent) { 238 if (mWifiManager == null) { 239 return; 240 } 241 String action = intent.getAction(); 242 if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 243 updateWifiState(); 244 } 245 } 246 updateWifiInfo(WifiInfo wifiInfo)247 private void updateWifiInfo(WifiInfo wifiInfo) { 248 updateWifiState(); 249 connected = wifiInfo != null; 250 mWifiInfo = wifiInfo; 251 ssid = null; 252 if (mWifiInfo != null) { 253 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) { 254 ssid = mWifiInfo.getPasspointProviderFriendlyName(); 255 } else { 256 ssid = getValidSsid(mWifiInfo); 257 } 258 if (mSupportMergedUi) { 259 isCarrierMerged = mWifiInfo.isCarrierMerged(); 260 subId = mWifiInfo.getSubscriptionId(); 261 } 262 updateRssi(mWifiInfo.getRssi()); 263 maybeRequestNetworkScore(); 264 } 265 } 266 updateWifiState()267 private void updateWifiState() { 268 state = mWifiManager.getWifiState(); 269 enabled = state == WifiManager.WIFI_STATE_ENABLED; 270 isCarrierMerged = false; 271 subId = 0; 272 } 273 updateRssi(int newRssi)274 private void updateRssi(int newRssi) { 275 rssi = newRssi; 276 level = mWifiManager.calculateSignalLevel(rssi); 277 } 278 maybeRequestNetworkScore()279 private void maybeRequestNetworkScore() { 280 NetworkKey networkKey = NetworkKey.createFromWifiInfo(mWifiInfo); 281 if (mWifiNetworkScoreCache.getScoredNetwork(networkKey) == null) { 282 mNetworkScoreManager.requestScores(new NetworkKey[]{ networkKey }); 283 } 284 } 285 updateStatusLabel()286 private void updateStatusLabel() { 287 if (mWifiManager == null) { 288 return; 289 } 290 NetworkCapabilities networkCapabilities; 291 isDefaultNetwork = false; 292 if (mDefaultNetworkCapabilities != null) { 293 boolean isWifi = mDefaultNetworkCapabilities.hasTransport( 294 NetworkCapabilities.TRANSPORT_WIFI); 295 boolean isVcnOverWifi = mDefaultNetworkCapabilities.hasTransport( 296 NetworkCapabilities.TRANSPORT_CELLULAR) 297 && (Utils.tryGetWifiInfoForVcn(mDefaultNetworkCapabilities) != null); 298 if (isWifi || isVcnOverWifi) { 299 isDefaultNetwork = true; 300 } 301 } 302 if (isDefaultNetwork) { 303 // Wifi is connected and the default network. 304 networkCapabilities = mDefaultNetworkCapabilities; 305 } else { 306 networkCapabilities = mConnectivityManager.getNetworkCapabilities( 307 mWifiManager.getCurrentNetwork()); 308 } 309 isCaptivePortal = false; 310 if (networkCapabilities != null) { 311 if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) { 312 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required); 313 isCaptivePortal = true; 314 return; 315 } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) { 316 statusLabel = mContext.getString(R.string.wifi_limited_connection); 317 return; 318 } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { 319 final String mode = Settings.Global.getString(mContext.getContentResolver(), 320 Settings.Global.PRIVATE_DNS_MODE); 321 if (networkCapabilities.isPrivateDnsBroken()) { 322 statusLabel = mContext.getString(R.string.private_dns_broken); 323 } else { 324 statusLabel = mContext.getString(R.string.wifi_status_no_internet); 325 } 326 return; 327 } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null 328 && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { 329 statusLabel = mContext.getString(R.string.wifi_connected_low_quality); 330 return; 331 } 332 } 333 334 ScoredNetwork scoredNetwork = 335 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo)); 336 statusLabel = scoredNetwork == null 337 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi); 338 } 339 340 /** Refresh the status label on Locale changed. */ refreshLocale()341 public void refreshLocale() { 342 updateStatusLabel(); 343 mCallback.run(); 344 } 345 getValidSsid(WifiInfo info)346 private String getValidSsid(WifiInfo info) { 347 String ssid = info.getSSID(); 348 if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) { 349 return ssid; 350 } 351 return null; 352 } 353 recordLastWifiNetwork(String log)354 private void recordLastWifiNetwork(String log) { 355 mHistory[mHistoryIndex] = log; 356 mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; 357 } 358 359 /** Dump function. */ dump(PrintWriter pw)360 public void dump(PrintWriter pw) { 361 pw.println(" - WiFi Network History ------"); 362 int size = 0; 363 for (int i = 0; i < HISTORY_SIZE; i++) { 364 if (mHistory[i] != null) size++; 365 } 366 // Print out the previous states in ordered number. 367 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 368 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 369 pw.println(" Previous WiFiNetwork(" 370 + (mHistoryIndex + HISTORY_SIZE - i) + "): " 371 + mHistory[i & (HISTORY_SIZE - 1)]); 372 } 373 } 374 } 375