1 /* 2 * Copyright 2017 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.server.wifi; 18 19 import android.content.Context; 20 import android.database.ContentObserver; 21 import android.net.wifi.ScanResult; 22 import android.net.wifi.WifiConfiguration; 23 import android.net.wifi.WifiManager; 24 import android.net.wifi.WifiNetworkSuggestion; 25 import android.net.wifi.WifiScanner; 26 import android.os.Handler; 27 import android.os.Process; 28 import android.provider.Settings; 29 import android.util.Log; 30 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.modules.utils.HandlerExecutor; 34 import com.android.server.wifi.util.LastCallerInfoManager; 35 import com.android.server.wifi.util.WifiPermissionsUtil; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 import java.util.Arrays; 40 import java.util.Collection; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Set; 44 import java.util.stream.Collectors; 45 46 47 /** 48 * WakeupController is responsible managing Auto Wifi. 49 * 50 * <p>It determines if and when to re-enable wifi after it has been turned off by the user. 51 */ 52 public class WakeupController { 53 54 private static final String TAG = "WakeupController"; 55 56 private static final boolean USE_PLATFORM_WIFI_WAKE = true; 57 private static final int INIT_WAKEUP_LOCK_SCAN_RESULT_VALID_DURATION_MS = 58 10 * 60 * 1000; // 10 minutes 59 60 private final Context mContext; 61 private final Handler mHandler; 62 private final FrameworkFacade mFrameworkFacade; 63 private final ContentObserver mContentObserver; 64 private final WakeupLock mWakeupLock; 65 private final WakeupEvaluator mWakeupEvaluator; 66 private final WakeupOnboarding mWakeupOnboarding; 67 private final WifiConfigManager mWifiConfigManager; 68 private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager; 69 private final WifiInjector mWifiInjector; 70 private final WakeupConfigStoreData mWakeupConfigStoreData; 71 private final WifiWakeMetrics mWifiWakeMetrics; 72 private final Clock mClock; 73 private final ActiveModeWarden mActiveModeWarden; 74 private final LastCallerInfoManager mLastCallerInfoManager; 75 private final WifiMetrics mWifiMetrics; 76 private final WifiPermissionsUtil mWifiPermissionsUtil; 77 private final WifiSettingsStore mSettingsStore; 78 private final Object mLock = new Object(); 79 80 private final WifiScanner.ScanListener mScanListener = new WifiScanner.ScanListener() { 81 @Override 82 public void onPeriodChanged(int periodInMs) { 83 // no-op 84 } 85 86 @Override 87 public void onResults(WifiScanner.ScanData[] results) { 88 // We treat any full band scans (with DFS or not) as "full". 89 if (results.length == 1 90 && WifiScanner.isFullBandScan(results[0].getScannedBandsInternal(), true)) { 91 handleScanResults(filterDfsScanResults(Arrays.asList(results[0].getResults()))); 92 } 93 } 94 95 @Override 96 public void onFullResult(ScanResult fullScanResult) { 97 // no-op 98 } 99 100 @Override 101 public void onSuccess() { 102 // no-op 103 } 104 105 @Override 106 public void onFailure(int reason, String description) { 107 Log.e(TAG, "ScanListener onFailure: " + reason + ": " + description); 108 } 109 }; 110 111 /** Whether this feature is enabled in Settings. */ 112 @GuardedBy("mLock") 113 private boolean mWifiWakeupEnabled; 114 115 /** Whether the WakeupController is currently active. */ 116 private boolean mIsActive = false; 117 118 /** 119 * The number of scans that have been handled by the controller since last 120 * {@link #onWifiEnabled()}. 121 */ 122 private int mNumScansHandled = 0; 123 124 /** Whether Wifi verbose logging is enabled. */ 125 private boolean mVerboseLoggingEnabled; 126 127 /** 128 * The timestamp of when the Wifi network was last disconnected (either device disconnected 129 * from the network or Wifi was turned off entirely). 130 * Note: mLastDisconnectTimestampMillis and mLastDisconnectInfo must always be updated together. 131 */ 132 private long mLastDisconnectTimestampMillis; 133 134 /** 135 * The SSID of the last Wifi network the device was connected to (either device disconnected 136 * from the network or Wifi was turned off entirely). 137 * Note: mLastDisconnectTimestampMillis and mLastDisconnectInfo must always be updated together. 138 */ 139 private ScanResultMatchInfo mLastDisconnectInfo; 140 WakeupController( Context context, Handler handler, WakeupLock wakeupLock, WakeupEvaluator wakeupEvaluator, WakeupOnboarding wakeupOnboarding, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager, WifiWakeMetrics wifiWakeMetrics, WifiInjector wifiInjector, FrameworkFacade frameworkFacade, Clock clock, ActiveModeWarden activeModeWarden)141 public WakeupController( 142 Context context, 143 Handler handler, 144 WakeupLock wakeupLock, 145 WakeupEvaluator wakeupEvaluator, 146 WakeupOnboarding wakeupOnboarding, 147 WifiConfigManager wifiConfigManager, 148 WifiConfigStore wifiConfigStore, 149 WifiNetworkSuggestionsManager wifiNetworkSuggestionsManager, 150 WifiWakeMetrics wifiWakeMetrics, 151 WifiInjector wifiInjector, 152 FrameworkFacade frameworkFacade, 153 Clock clock, 154 ActiveModeWarden activeModeWarden) { 155 mContext = context; 156 mHandler = handler; 157 mWakeupLock = wakeupLock; 158 mWakeupEvaluator = wakeupEvaluator; 159 mWakeupOnboarding = wakeupOnboarding; 160 mWifiConfigManager = wifiConfigManager; 161 mWifiNetworkSuggestionsManager = wifiNetworkSuggestionsManager; 162 mWifiWakeMetrics = wifiWakeMetrics; 163 mFrameworkFacade = frameworkFacade; 164 mWifiInjector = wifiInjector; 165 mActiveModeWarden = activeModeWarden; 166 mLastCallerInfoManager = wifiInjector.getLastCallerInfoManager(); 167 mWifiMetrics = wifiInjector.getWifiMetrics(); 168 mWifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil(); 169 mSettingsStore = wifiInjector.getWifiSettingsStore(); 170 mContentObserver = new ContentObserver(mHandler) { 171 @Override 172 public void onChange(boolean selfChange) { 173 readWifiWakeupEnabledFromSettings(); 174 mWakeupOnboarding.setOnboarded(); 175 } 176 }; 177 mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor( 178 Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver); 179 readWifiWakeupEnabledFromSettings(); 180 181 // registering the store data here has the effect of reading the persisted value of the 182 // data sources after system boot finishes 183 mWakeupConfigStoreData = new WakeupConfigStoreData( 184 new IsActiveDataSource(), 185 mWakeupOnboarding.getIsOnboadedDataSource(), 186 mWakeupOnboarding.getNotificationsDataSource(), 187 mWakeupLock.getDataSource()); 188 wifiConfigStore.registerStoreData(mWakeupConfigStoreData); 189 mClock = clock; 190 mLastDisconnectTimestampMillis = 0; 191 mLastDisconnectInfo = null; 192 193 mActiveModeWarden.registerPrimaryClientModeManagerChangedCallback( 194 (prevPrimaryClientModeManager, newPrimaryClientModeManager) -> { 195 // reset when the primary CMM changes 196 if (newPrimaryClientModeManager != null) { 197 onWifiEnabled(); 198 } 199 }); 200 } 201 readWifiWakeupEnabledFromSettings()202 private void readWifiWakeupEnabledFromSettings() { 203 synchronized (mLock) { 204 mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting( 205 mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1; 206 Log.d(TAG, "WifiWake " + (mWifiWakeupEnabled ? "enabled" : "disabled")); 207 } 208 } 209 setActive(boolean isActive)210 private void setActive(boolean isActive) { 211 if (mIsActive != isActive) { 212 Log.d(TAG, "Setting active to " + isActive); 213 mIsActive = isActive; 214 mWifiConfigManager.saveToStore(); 215 } 216 } 217 218 /** 219 * Enable/Disable the feature. 220 */ setEnabled(boolean enable)221 public void setEnabled(boolean enable) { 222 synchronized (mLock) { 223 mFrameworkFacade.setIntegerSetting( 224 mContext, Settings.Global.WIFI_WAKEUP_ENABLED, enable ? 1 : 0); 225 } 226 } 227 228 /** 229 * Whether the feature is currently enabled. 230 */ isEnabled()231 public boolean isEnabled() { 232 synchronized (mLock) { 233 return mWifiWakeupEnabled; 234 } 235 } 236 237 /** 238 * Whether the feature is currently usable. 239 */ isUsable()240 public boolean isUsable() { 241 return isEnabled() 242 && mSettingsStore.isScanAlwaysAvailableToggleEnabled() 243 && mWifiPermissionsUtil.isLocationModeEnabled(); 244 } 245 246 /** 247 * Saves the SSID of the last Wifi network that was disconnected. Should only be called before 248 * WakeupController is active. 249 */ setLastDisconnectInfo(ScanResultMatchInfo scanResultMatchInfo)250 public void setLastDisconnectInfo(ScanResultMatchInfo scanResultMatchInfo) { 251 if (mIsActive) { 252 Log.e(TAG, "Unexpected setLastDisconnectInfo when WakeupController is active!"); 253 return; 254 } 255 if (scanResultMatchInfo == null) { 256 Log.e(TAG, "Unexpected setLastDisconnectInfo(null)"); 257 return; 258 } 259 mLastDisconnectTimestampMillis = mClock.getElapsedSinceBootMillis(); 260 mLastDisconnectInfo = scanResultMatchInfo; 261 if (mVerboseLoggingEnabled) { 262 Log.d(TAG, "mLastDisconnectInfo set to " + scanResultMatchInfo); 263 } 264 } 265 266 /** 267 * If Wifi was disabled within LAST_DISCONNECT_TIMEOUT_MILLIS of losing a Wifi connection, 268 * add that Wifi connection to the Wakeup Lock as if Wifi was disabled while connected to that 269 * connection. 270 * Often times, networks with poor signal intermittently connect and disconnect, causing the 271 * user to manually turn off Wifi. If the Wifi was turned off during the disconnected phase of 272 * the intermittent connection, then that connection normally would not be added to the Wakeup 273 * Lock. This constant defines the timeout after disconnecting, in milliseconds, within which 274 * if Wifi was disabled, the network would still be added to the wakeup lock. 275 */ 276 @VisibleForTesting 277 static final long LAST_DISCONNECT_TIMEOUT_MILLIS = 5 * 1000; 278 279 /** 280 * Starts listening for incoming scans. 281 * 282 * <p>Should only be called upon entering ScanMode. WakeupController registers its listener with 283 * the WifiScanner. If the WakeupController is already active, then it returns early. Otherwise 284 * it performs its initialization steps and sets {@link #mIsActive} to true. 285 */ start()286 public void start() { 287 Log.d(TAG, "start()"); 288 // If already active, we don't want to restart the session, so return early. 289 if (mIsActive) { 290 mWifiWakeMetrics.recordIgnoredStart(); 291 return; 292 } 293 if (getGoodSavedNetworksAndSuggestions().isEmpty()) { 294 Log.i(TAG, "Ignore wakeup start since there are no good networks."); 295 return; 296 } 297 mWifiInjector.getWifiScanner().registerScanListener( 298 new HandlerExecutor(mHandler), mScanListener); 299 300 setActive(true); 301 302 // ensure feature is enabled and store data has been read before performing work 303 if (isEnabledAndReady()) { 304 mWakeupOnboarding.maybeShowNotification(); 305 306 List<ScanResult> scanResults = filterDfsScanResults( 307 mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks( 308 INIT_WAKEUP_LOCK_SCAN_RESULT_VALID_DURATION_MS)); 309 Set<ScanResultMatchInfo> matchInfos = toMatchInfos(scanResults); 310 matchInfos.retainAll(getGoodSavedNetworksAndSuggestions()); 311 312 // ensure that the last disconnected network is added to the wakeup lock, since we don't 313 // want to automatically reconnect to the same network that the user manually 314 // disconnected from 315 long now = mClock.getElapsedSinceBootMillis(); 316 if (mLastDisconnectInfo != null && ((now - mLastDisconnectTimestampMillis) 317 <= LAST_DISCONNECT_TIMEOUT_MILLIS)) { 318 matchInfos.add(mLastDisconnectInfo); 319 if (mVerboseLoggingEnabled) { 320 Log.d(TAG, "Added last connected network to lock: " + mLastDisconnectInfo); 321 } 322 } 323 324 if (mVerboseLoggingEnabled) { 325 Log.d(TAG, "Saved networks in most recent scan:" + matchInfos); 326 } 327 328 mWifiWakeMetrics.recordStartEvent(matchInfos.size()); 329 mWakeupLock.setLock(matchInfos); 330 // TODO(b/77291248): request low latency scan here 331 } 332 } 333 334 /** 335 * Stops listening for scans. 336 * 337 * <p>Should only be called upon leaving ScanMode. It deregisters the listener from 338 * WifiScanner. 339 */ stop()340 public void stop() { 341 Log.d(TAG, "stop()"); 342 mLastDisconnectTimestampMillis = 0; 343 mLastDisconnectInfo = null; 344 mWifiInjector.getWifiScanner().unregisterScanListener(mScanListener); 345 mWakeupOnboarding.onStop(); 346 } 347 348 /** 349 * This is called at the end of a Wifi Wake session, after Wifi Wake successfully turned Wifi 350 * back on. 351 */ onWifiEnabled()352 private void onWifiEnabled() { 353 Log.d(TAG, "onWifiEnabled()"); 354 mWifiWakeMetrics.recordResetEvent(mNumScansHandled); 355 mNumScansHandled = 0; 356 setActive(false); 357 } 358 359 /** Sets verbose logging flag based on verbose level. */ enableVerboseLogging(boolean verboseEnabled)360 public void enableVerboseLogging(boolean verboseEnabled) { 361 mVerboseLoggingEnabled = verboseEnabled; 362 mWakeupLock.enableVerboseLogging(mVerboseLoggingEnabled); 363 } 364 365 /** Returns a list of ScanResults with DFS channels removed. */ filterDfsScanResults(Collection<ScanResult> scanResults)366 private List<ScanResult> filterDfsScanResults(Collection<ScanResult> scanResults) { 367 int[] dfsChannels = mWifiInjector.getWifiNative() 368 .getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY); 369 if (dfsChannels == null) { 370 dfsChannels = new int[0]; 371 } 372 373 final Set<Integer> dfsChannelSet = Arrays.stream(dfsChannels).boxed() 374 .collect(Collectors.toSet()); 375 376 return scanResults.stream() 377 .filter(scanResult -> !dfsChannelSet.contains(scanResult.frequency)) 378 .collect(Collectors.toList()); 379 } 380 381 /** Returns a filtered set of saved networks from WifiConfigManager & suggestions 382 * from WifiNetworkSuggestionsManager. */ getGoodSavedNetworksAndSuggestions()383 private Set<ScanResultMatchInfo> getGoodSavedNetworksAndSuggestions() { 384 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks( 385 Process.WIFI_UID); 386 387 Set<ScanResultMatchInfo> goodNetworks = new HashSet<>(savedNetworks.size()); 388 for (WifiConfiguration config : savedNetworks) { 389 if (config.hasNoInternetAccess() 390 || config.noInternetAccessExpected 391 || !config.getNetworkSelectionStatus().hasEverConnected() 392 || !config.allowAutojoin 393 || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled() 394 || (!config.getNetworkSelectionStatus().hasNeverDetectedCaptivePortal() 395 && !config.validatedInternetAccess)) { 396 continue; 397 } 398 goodNetworks.add(ScanResultMatchInfo.fromWifiConfiguration(config)); 399 } 400 401 Set<WifiNetworkSuggestion> networkSuggestions = 402 mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions(); 403 for (WifiNetworkSuggestion suggestion : networkSuggestions) { 404 // TODO(b/127799111): Do we need to filter the list similar to saved networks above? 405 goodNetworks.add( 406 ScanResultMatchInfo.fromWifiConfiguration(suggestion.wifiConfiguration)); 407 } 408 return goodNetworks; 409 } 410 411 /** 412 * Handles incoming scan results. 413 * 414 * <p>The controller updates the WakeupLock with the incoming scan results. If WakeupLock is not 415 * yet fully initialized, it adds the current scanResults to the lock and returns. If WakeupLock 416 * is initialized but not empty, the controller updates the lock with the current scan. If it is 417 * both initialized and empty, it evaluates scan results for a match with saved networks. If a 418 * match exists, it enables wifi. 419 * 420 * <p>The feature must be enabled and the store data must be loaded in order for the controller 421 * to handle scan results. 422 * 423 * @param scanResults The scan results with which to update the controller 424 */ handleScanResults(Collection<ScanResult> scanResults)425 private void handleScanResults(Collection<ScanResult> scanResults) { 426 if (!isEnabledAndReady() || !mIsActive) { 427 Log.d(TAG, "Attempted to handleScanResults while not enabled"); 428 return; 429 } 430 431 // only count scan as handled if isEnabledAndReady 432 mNumScansHandled++; 433 if (mVerboseLoggingEnabled) { 434 Log.d(TAG, "Incoming scan #" + mNumScansHandled); 435 } 436 437 // need to show notification here in case user turns phone on while wifi is off 438 mWakeupOnboarding.maybeShowNotification(); 439 440 // filter out unknown networks 441 Set<ScanResultMatchInfo> goodNetworks = getGoodSavedNetworksAndSuggestions(); 442 Set<ScanResultMatchInfo> matchInfos = toMatchInfos(scanResults); 443 matchInfos.retainAll(goodNetworks); 444 445 mWakeupLock.update(matchInfos); 446 if (!mWakeupLock.isUnlocked()) { 447 return; 448 } 449 450 ScanResult network = mWakeupEvaluator.findViableNetwork(scanResults, goodNetworks); 451 452 if (network != null) { 453 Log.d(TAG, "Enabling wifi for network: " + network.SSID); 454 enableWifi(); 455 } 456 } 457 458 /** 459 * Converts ScanResults to ScanResultMatchInfos. 460 */ toMatchInfos(Collection<ScanResult> scanResults)461 private static Set<ScanResultMatchInfo> toMatchInfos(Collection<ScanResult> scanResults) { 462 return scanResults.stream() 463 .map(ScanResultMatchInfo::fromScanResult) 464 .collect(Collectors.toSet()); 465 } 466 467 /** 468 * Enables wifi. 469 * 470 * <p>This method ignores all checks and assumes that {@link ActiveModeWarden} is currently 471 * in ScanModeState. 472 */ enableWifi()473 private void enableWifi() { 474 if (USE_PLATFORM_WIFI_WAKE) { 475 // TODO(b/72180295): ensure that there is no race condition with WifiServiceImpl here 476 if (mWifiInjector.getWifiSettingsStore().handleWifiToggled(true /* wifiEnabled */)) { 477 mActiveModeWarden.wifiToggled( 478 // Assumes user toggled it on from settings before. 479 mFrameworkFacade.getSettingsWorkSource(mContext)); 480 mWifiWakeMetrics.recordWakeupEvent(mNumScansHandled); 481 mWifiMetrics.reportWifiStateChanged(true, isUsable(), true); 482 mLastCallerInfoManager.put(WifiManager.API_WIFI_ENABLED, Process.myTid(), 483 Process.WIFI_UID, -1, "android_wifi_wake", true); 484 } 485 } 486 } 487 488 /** 489 * Whether the feature is currently enabled and usable. 490 * 491 * <p>This method checks both the Settings values and the store data to ensure that it has been 492 * read. 493 */ 494 @VisibleForTesting isEnabledAndReady()495 boolean isEnabledAndReady() { 496 return isUsable() && mWakeupConfigStoreData.hasBeenRead(); 497 } 498 499 /** Dumps wakeup controller state. */ dump(FileDescriptor fd, PrintWriter pw, String[] args)500 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 501 pw.println("Dump of WakeupController"); 502 pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE); 503 pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled); 504 pw.println("isOnboarded: " + mWakeupOnboarding.isOnboarded()); 505 pw.println("configStore hasBeenRead: " + mWakeupConfigStoreData.hasBeenRead()); 506 pw.println("mIsActive: " + mIsActive); 507 pw.println("mNumScansHandled: " + mNumScansHandled); 508 509 mWakeupLock.dump(fd, pw, args); 510 } 511 512 private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> { 513 514 @Override getData()515 public Boolean getData() { 516 return mIsActive; 517 } 518 519 @Override setData(Boolean data)520 public void setData(Boolean data) { 521 mIsActive = data; 522 } 523 } 524 resetNotification()525 public void resetNotification() { 526 mWakeupOnboarding.onStop(); 527 } 528 } 529