1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wifitrackerlib; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 22 23 import static java.util.stream.Collectors.toList; 24 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.net.ConnectivityManager; 30 import android.net.LinkProperties; 31 import android.net.Network; 32 import android.net.NetworkCapabilities; 33 import android.net.NetworkKey; 34 import android.net.NetworkRequest; 35 import android.net.NetworkScoreManager; 36 import android.net.ScoredNetwork; 37 import android.net.TransportInfo; 38 import android.net.vcn.VcnTransportInfo; 39 import android.net.wifi.WifiInfo; 40 import android.net.wifi.WifiManager; 41 import android.net.wifi.WifiNetworkScoreCache; 42 import android.os.Handler; 43 import android.os.Looper; 44 import android.telephony.SubscriptionManager; 45 import android.telephony.TelephonyManager; 46 import android.util.Log; 47 48 import androidx.annotation.AnyThread; 49 import androidx.annotation.MainThread; 50 import androidx.annotation.NonNull; 51 import androidx.annotation.Nullable; 52 import androidx.annotation.WorkerThread; 53 import androidx.lifecycle.Lifecycle; 54 import androidx.lifecycle.LifecycleObserver; 55 import androidx.lifecycle.OnLifecycleEvent; 56 57 import java.time.Clock; 58 import java.util.HashSet; 59 import java.util.List; 60 import java.util.Set; 61 62 /** 63 * Base class for WifiTracker functionality. 64 * 65 * This class provides the basic functions of issuing scans, receiving Wi-Fi related broadcasts, and 66 * keeping track of the Wi-Fi state. 67 * 68 * Subclasses are expected to provide their own API to clients and override the empty broadcast 69 * handling methods here to populate the data returned by their API. 70 * 71 * This class runs on two threads: 72 * 73 * The main thread 74 * - Processes lifecycle events (onStart, onStop) 75 * - Runs listener callbacks 76 * 77 * The worker thread 78 * - Drives the periodic scan requests 79 * - Handles the system broadcasts to update the API return values 80 * - Notifies the listener for updates to the API return values 81 * 82 * To keep synchronization simple, this means that the vast majority of work is done within the 83 * worker thread. Synchronized blocks are only to be used for data returned by the API updated by 84 * the worker thread and consumed by the main thread. 85 */ 86 87 public class BaseWifiTracker implements LifecycleObserver { 88 private final String mTag; 89 90 private static boolean sVerboseLogging; 91 isVerboseLoggingEnabled()92 public static boolean isVerboseLoggingEnabled() { 93 return BaseWifiTracker.sVerboseLogging; 94 } 95 96 private boolean mIsStarted; 97 98 // Registered on the worker thread 99 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 100 @Override 101 @WorkerThread 102 public void onReceive(Context context, Intent intent) { 103 if (!mIsStarted) { 104 mIsStarted = true; 105 handleOnStart(); 106 } 107 108 String action = intent.getAction(); 109 110 if (isVerboseLoggingEnabled()) { 111 Log.v(mTag, "Received broadcast: " + action); 112 } 113 114 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 115 if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) { 116 mScanner.start(); 117 } else { 118 mScanner.stop(); 119 } 120 notifyOnWifiStateChanged(); 121 handleWifiStateChangedAction(); 122 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { 123 mNetworkScoreManager.requestScores(mWifiManager.getScanResults().stream() 124 .map(NetworkKey::createFromScanResult) 125 .filter(mRequestedScoreKeys::add) 126 .collect(toList())); 127 handleScanResultsAvailableAction(intent); 128 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)) { 129 handleConfiguredNetworksChangedAction(intent); 130 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 131 handleNetworkStateChangedAction(intent); 132 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 133 handleRssiChangedAction(); 134 } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) { 135 handleDefaultSubscriptionChanged(intent.getIntExtra( 136 "subscription", SubscriptionManager.INVALID_SUBSCRIPTION_ID)); 137 } 138 } 139 }; 140 private final BaseWifiTracker.Scanner mScanner; 141 private final BaseWifiTrackerCallback mListener; 142 143 protected final WifiTrackerInjector mInjector; 144 protected final Context mContext; 145 protected final WifiManager mWifiManager; 146 protected final ConnectivityManager mConnectivityManager; 147 protected final NetworkScoreManager mNetworkScoreManager; 148 protected final Handler mMainHandler; 149 protected final Handler mWorkerHandler; 150 protected final long mMaxScanAgeMillis; 151 protected final long mScanIntervalMillis; 152 protected final ScanResultUpdater mScanResultUpdater; 153 protected final WifiNetworkScoreCache mWifiNetworkScoreCache; 154 protected boolean mIsWifiValidated; 155 protected boolean mIsWifiDefaultRoute; 156 protected boolean mIsCellDefaultRoute; 157 private final Set<NetworkKey> mRequestedScoreKeys = new HashSet<>(); 158 159 // Network request for listening on changes to Wifi link properties and network capabilities 160 // such as captive portal availability. 161 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 162 .clearCapabilities() 163 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 164 .addTransportType(TRANSPORT_WIFI) 165 .build(); 166 167 private final ConnectivityManager.NetworkCallback mNetworkCallback = 168 new ConnectivityManager.NetworkCallback() { 169 @Override 170 @WorkerThread 171 public void onLinkPropertiesChanged(@NonNull Network network, 172 @NonNull LinkProperties lp) { 173 if (!mIsStarted) { 174 mIsStarted = true; 175 handleOnStart(); 176 } 177 if (!isPrimaryWifiNetwork( 178 mConnectivityManager.getNetworkCapabilities(network))) { 179 return; 180 } 181 handleLinkPropertiesChanged(lp); 182 } 183 184 @Override 185 @WorkerThread 186 public void onCapabilitiesChanged(@NonNull Network network, 187 @NonNull NetworkCapabilities networkCapabilities) { 188 if (!mIsStarted) { 189 mIsStarted = true; 190 handleOnStart(); 191 } 192 if (!isPrimaryWifiNetwork(networkCapabilities)) { 193 return; 194 } 195 final boolean oldWifiValidated = mIsWifiValidated; 196 mIsWifiValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED); 197 if (isVerboseLoggingEnabled() && mIsWifiValidated != oldWifiValidated) { 198 Log.v(mTag, "Is Wifi validated: " + mIsWifiValidated); 199 } 200 handleNetworkCapabilitiesChanged(networkCapabilities); 201 } 202 203 @Override 204 @WorkerThread 205 public void onLost(@NonNull Network network) { 206 if (!mIsStarted) { 207 mIsStarted = true; 208 handleOnStart(); 209 } 210 if (!isPrimaryWifiNetwork( 211 mConnectivityManager.getNetworkCapabilities(network))) { 212 return; 213 } 214 mIsWifiValidated = false; 215 } 216 }; 217 218 private final ConnectivityManager.NetworkCallback mDefaultNetworkCallback = 219 new ConnectivityManager.NetworkCallback() { 220 @Override 221 @WorkerThread 222 public void onCapabilitiesChanged(@NonNull Network network, 223 @NonNull NetworkCapabilities networkCapabilities) { 224 if (!mIsStarted) { 225 mIsStarted = true; 226 handleOnStart(); 227 } 228 final boolean oldWifiDefault = mIsWifiDefaultRoute; 229 final boolean oldCellDefault = mIsCellDefaultRoute; 230 TransportInfo transportInfo = networkCapabilities.getTransportInfo(); 231 final boolean isVcnOverWifi = transportInfo != null 232 && transportInfo instanceof VcnTransportInfo 233 && ((VcnTransportInfo) transportInfo).getWifiInfo() != null; 234 // raw Wifi or VPN-over-Wifi or VCN-over-Wifi is default => Wifi is default. 235 mIsWifiDefaultRoute = networkCapabilities.hasTransport(TRANSPORT_WIFI) 236 || isVcnOverWifi; 237 mIsCellDefaultRoute = !mIsWifiDefaultRoute 238 && networkCapabilities.hasTransport(TRANSPORT_CELLULAR); 239 if (mIsWifiDefaultRoute != oldWifiDefault 240 || mIsCellDefaultRoute != oldCellDefault) { 241 if (isVerboseLoggingEnabled()) { 242 Log.v(mTag, "Wifi is the default route: " + mIsWifiDefaultRoute); 243 Log.v(mTag, "Cell is the default route: " + mIsCellDefaultRoute); 244 } 245 handleDefaultRouteChanged(); 246 } 247 } 248 249 @WorkerThread 250 public void onLost(@NonNull Network network) { 251 if (!mIsStarted) { 252 mIsStarted = true; 253 handleOnStart(); 254 } 255 mIsWifiDefaultRoute = false; 256 mIsCellDefaultRoute = false; 257 if (isVerboseLoggingEnabled()) { 258 Log.v(mTag, "Wifi is the default route: false"); 259 Log.v(mTag, "Cell is the default route: false"); 260 } 261 handleDefaultRouteChanged(); 262 } 263 }; 264 isPrimaryWifiNetwork(@ullable NetworkCapabilities networkCapabilities)265 private boolean isPrimaryWifiNetwork(@Nullable NetworkCapabilities networkCapabilities) { 266 if (networkCapabilities == null) { 267 return false; 268 } 269 final TransportInfo transportInfo = networkCapabilities.getTransportInfo(); 270 if (!(transportInfo instanceof WifiInfo)) { 271 return false; 272 } 273 return ((WifiInfo) transportInfo).isPrimary(); 274 } 275 276 /** 277 * Constructor for BaseWifiTracker. 278 * 279 * @param wifiTrackerInjector injector for commonly referenced objects. 280 * @param lifecycle Lifecycle this is tied to for lifecycle callbacks. 281 * @param context Context for registering broadcast receiver and for resource strings. 282 * @param wifiManager Provides all Wi-Fi info. 283 * @param connectivityManager Provides network info. 284 * @param networkScoreManager Provides network scores for network badging. 285 * @param mainHandler Handler for processing listener callbacks. 286 * @param workerHandler Handler for processing all broadcasts and running the Scanner. 287 * @param clock Clock used for evaluating the age of scans 288 * @param maxScanAgeMillis Max age for tracked WifiEntries. 289 * @param scanIntervalMillis Interval between initiating scans. 290 */ BaseWifiTracker( @onNull WifiTrackerInjector injector, @NonNull Lifecycle lifecycle, @NonNull Context context, @NonNull WifiManager wifiManager, @NonNull ConnectivityManager connectivityManager, @NonNull NetworkScoreManager networkScoreManager, @NonNull Handler mainHandler, @NonNull Handler workerHandler, @NonNull Clock clock, long maxScanAgeMillis, long scanIntervalMillis, BaseWifiTrackerCallback listener, String tag)291 BaseWifiTracker( 292 @NonNull WifiTrackerInjector injector, 293 @NonNull Lifecycle lifecycle, @NonNull Context context, 294 @NonNull WifiManager wifiManager, 295 @NonNull ConnectivityManager connectivityManager, 296 @NonNull NetworkScoreManager networkScoreManager, 297 @NonNull Handler mainHandler, 298 @NonNull Handler workerHandler, 299 @NonNull Clock clock, 300 long maxScanAgeMillis, 301 long scanIntervalMillis, 302 BaseWifiTrackerCallback listener, 303 String tag) { 304 mInjector = injector; 305 lifecycle.addObserver(this); 306 mContext = context; 307 mWifiManager = wifiManager; 308 mConnectivityManager = connectivityManager; 309 mNetworkScoreManager = networkScoreManager; 310 mMainHandler = mainHandler; 311 mWorkerHandler = workerHandler; 312 mMaxScanAgeMillis = maxScanAgeMillis; 313 mScanIntervalMillis = scanIntervalMillis; 314 mListener = listener; 315 mTag = tag; 316 317 mScanResultUpdater = new ScanResultUpdater(clock, 318 maxScanAgeMillis + scanIntervalMillis); 319 mWifiNetworkScoreCache = new WifiNetworkScoreCache(mContext, 320 new WifiNetworkScoreCache.CacheListener(mWorkerHandler) { 321 @Override 322 public void networkCacheUpdated(List<ScoredNetwork> networks) { 323 handleNetworkScoreCacheUpdated(); 324 } 325 }); 326 mScanner = new BaseWifiTracker.Scanner(workerHandler.getLooper()); 327 sVerboseLogging = mWifiManager.isVerboseLoggingEnabled(); 328 } 329 330 /** 331 * Registers the broadcast receiver and network callbacks and starts the scanning mechanism. 332 */ 333 @OnLifecycleEvent(Lifecycle.Event.ON_START) 334 @MainThread onStart()335 public void onStart() { 336 IntentFilter filter = new IntentFilter(); 337 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 338 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 339 filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); 340 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 341 filter.addAction(WifiManager.RSSI_CHANGED_ACTION); 342 filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); 343 mContext.registerReceiver(mBroadcastReceiver, filter, 344 /* broadcastPermission */ null, mWorkerHandler); 345 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback, 346 mWorkerHandler); 347 mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, 348 mWorkerHandler); 349 final NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager 350 .getNetworkCapabilities(mConnectivityManager.getActiveNetwork()); 351 if (defaultNetworkCapabilities != null) { 352 mIsWifiDefaultRoute = defaultNetworkCapabilities.hasTransport(TRANSPORT_WIFI); 353 mIsCellDefaultRoute = defaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR); 354 } else { 355 mIsWifiDefaultRoute = false; 356 mIsCellDefaultRoute = false; 357 } 358 if (isVerboseLoggingEnabled()) { 359 Log.v(mTag, "Wifi is the default route: " + mIsWifiDefaultRoute); 360 Log.v(mTag, "Cell is the default route: " + mIsCellDefaultRoute); 361 } 362 363 mNetworkScoreManager.registerNetworkScoreCache( 364 NetworkKey.TYPE_WIFI, 365 mWifiNetworkScoreCache, 366 NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); 367 mWorkerHandler.post(() -> { 368 if (!mIsStarted) { 369 mIsStarted = true; 370 handleOnStart(); 371 } 372 }); 373 } 374 375 /** 376 * Unregisters the broadcast receiver, network callbacks, and pauses the scanning mechanism. 377 */ 378 @OnLifecycleEvent(Lifecycle.Event.ON_STOP) 379 @MainThread onStop()380 public void onStop() { 381 mWorkerHandler.post(mScanner::stop); 382 mContext.unregisterReceiver(mBroadcastReceiver); 383 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 384 mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback); 385 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, 386 mWifiNetworkScoreCache); 387 mWorkerHandler.post(mRequestedScoreKeys::clear); 388 mIsStarted = false; 389 } 390 391 /** 392 * Returns the state of Wi-Fi as one of the following values. 393 * 394 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li> 395 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li> 396 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li> 397 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li> 398 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li> 399 */ 400 @AnyThread getWifiState()401 public int getWifiState() { 402 return mWifiManager.getWifiState(); 403 } 404 405 /** 406 * Method to run on the worker thread when onStart is invoked. 407 * Data that can be updated immediately after onStart should be populated here. 408 */ 409 @WorkerThread handleOnStart()410 protected void handleOnStart() { 411 // Do nothing. 412 } 413 414 /** 415 * Handle receiving the WifiManager.WIFI_STATE_CHANGED_ACTION broadcast 416 */ 417 @WorkerThread handleWifiStateChangedAction()418 protected void handleWifiStateChangedAction() { 419 // Do nothing. 420 } 421 422 /** 423 * Handle receiving the WifiManager.SCAN_RESULTS_AVAILABLE_ACTION broadcast 424 */ 425 @WorkerThread handleScanResultsAvailableAction(@onNull Intent intent)426 protected void handleScanResultsAvailableAction(@NonNull Intent intent) { 427 // Do nothing. 428 } 429 430 /** 431 * Handle receiving the WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION broadcast 432 */ 433 @WorkerThread handleConfiguredNetworksChangedAction(@onNull Intent intent)434 protected void handleConfiguredNetworksChangedAction(@NonNull Intent intent) { 435 // Do nothing. 436 } 437 438 /** 439 * Handle receiving the WifiManager.NETWORK_STATE_CHANGED_ACTION broadcast 440 */ 441 @WorkerThread handleNetworkStateChangedAction(@onNull Intent intent)442 protected void handleNetworkStateChangedAction(@NonNull Intent intent) { 443 // Do nothing. 444 } 445 446 /** 447 * Handle receiving the WifiManager.RSSI_CHANGED_ACTION broadcast 448 */ 449 @WorkerThread handleRssiChangedAction()450 protected void handleRssiChangedAction() { 451 // Do nothing. 452 } 453 454 /** 455 * Handle link property changes for the current connected Wifi network. 456 */ 457 @WorkerThread handleLinkPropertiesChanged(@ullable LinkProperties linkProperties)458 protected void handleLinkPropertiesChanged(@Nullable LinkProperties linkProperties) { 459 // Do nothing. 460 } 461 462 /** 463 * Handle network capability changes for the current connected Wifi network. 464 */ 465 @WorkerThread handleNetworkCapabilitiesChanged(@ullable NetworkCapabilities capabilities)466 protected void handleNetworkCapabilitiesChanged(@Nullable NetworkCapabilities capabilities) { 467 // Do nothing. 468 } 469 470 /** 471 * Handle when the default route changes. Whether Wifi is the default route is stored in 472 * mIsWifiDefaultRoute. 473 */ 474 @WorkerThread handleDefaultRouteChanged()475 protected void handleDefaultRouteChanged() { 476 // Do nothing. 477 } 478 479 /** 480 * Handle updates to the Wifi network score cache, which is stored in mWifiNetworkScoreCache 481 */ 482 @WorkerThread handleNetworkScoreCacheUpdated()483 protected void handleNetworkScoreCacheUpdated() { 484 // Do nothing. 485 } 486 487 /** 488 * Handle updates to the default data subscription id from SubscriptionManager. 489 */ 490 @WorkerThread handleDefaultSubscriptionChanged(int defaultSubId)491 protected void handleDefaultSubscriptionChanged(int defaultSubId) { 492 // Do nothing. 493 } 494 495 /** 496 * Scanner to handle starting scans every SCAN_INTERVAL_MILLIS 497 */ 498 @WorkerThread 499 private class Scanner extends Handler { 500 private static final int SCAN_RETRY_TIMES = 3; 501 502 private int mRetry = 0; 503 private boolean mIsActive; 504 Scanner(Looper looper)505 private Scanner(Looper looper) { 506 super(looper); 507 } 508 start()509 private void start() { 510 if (!mIsActive) { 511 mIsActive = true; 512 if (isVerboseLoggingEnabled()) { 513 Log.v(mTag, "Scanner start"); 514 } 515 postScan(); 516 } 517 } 518 stop()519 private void stop() { 520 mIsActive = false; 521 if (isVerboseLoggingEnabled()) { 522 Log.v(mTag, "Scanner stop"); 523 } 524 mRetry = 0; 525 removeCallbacksAndMessages(null); 526 } 527 postScan()528 private void postScan() { 529 if (mWifiManager.startScan()) { 530 mRetry = 0; 531 } else if (++mRetry >= SCAN_RETRY_TIMES) { 532 // TODO(b/70983952): See if toast is needed here 533 if (isVerboseLoggingEnabled()) { 534 Log.v(mTag, "Scanner failed to start scan " + mRetry + " times!"); 535 } 536 mRetry = 0; 537 return; 538 } 539 postDelayed(this::postScan, mScanIntervalMillis); 540 } 541 } 542 543 /** 544 * Posts onWifiStateChanged callback on the main thread. 545 */ 546 @WorkerThread notifyOnWifiStateChanged()547 private void notifyOnWifiStateChanged() { 548 if (mListener != null) { 549 mMainHandler.post(mListener::onWifiStateChanged); 550 } 551 } 552 553 /** 554 * Base callback handling Wi-Fi state changes 555 * 556 * Subclasses should extend this for their own needs. 557 */ 558 protected interface BaseWifiTrackerCallback { 559 /** 560 * Called when the value for {@link #getWifiState() has changed. 561 */ 562 @MainThread onWifiStateChanged()563 void onWifiStateChanged(); 564 } 565 } 566