WakeupController.java revision 5aad8ece7e38a80db917d49b5245f6b8c6dca273
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 17package com.android.server.wifi; 18 19import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED; 20 21import android.content.Context; 22import android.database.ContentObserver; 23import android.net.wifi.ScanResult; 24import android.net.wifi.WifiConfiguration; 25import android.net.wifi.WifiScanner; 26import android.os.Handler; 27import android.os.Looper; 28import android.provider.Settings; 29import android.util.Log; 30 31import com.android.internal.annotations.VisibleForTesting; 32 33import java.io.FileDescriptor; 34import java.io.PrintWriter; 35import java.util.Arrays; 36import java.util.Collection; 37import java.util.HashSet; 38import java.util.List; 39import java.util.Set; 40import java.util.stream.Collectors; 41 42 43/** 44 * WakeupController is responsible managing Auto Wifi. 45 * 46 * <p>It determines if and when to re-enable wifi after it has been turned off by the user. 47 */ 48public class WakeupController { 49 50 private static final String TAG = "WakeupController"; 51 52 private static final boolean USE_PLATFORM_WIFI_WAKE = true; 53 54 private final Context mContext; 55 private final Handler mHandler; 56 private final FrameworkFacade mFrameworkFacade; 57 private final ContentObserver mContentObserver; 58 private final WakeupLock mWakeupLock; 59 private final WakeupEvaluator mWakeupEvaluator; 60 private final WakeupOnboarding mWakeupOnboarding; 61 private final WifiConfigManager mWifiConfigManager; 62 private final WifiInjector mWifiInjector; 63 private final WakeupConfigStoreData mWakeupConfigStoreData; 64 private final WifiWakeMetrics mWifiWakeMetrics; 65 66 private final WifiScanner.ScanListener mScanListener = new WifiScanner.ScanListener() { 67 @Override 68 public void onPeriodChanged(int periodInMs) { 69 // no-op 70 } 71 72 @Override 73 public void onResults(WifiScanner.ScanData[] results) { 74 if (results.length == 1 && results[0].isAllChannelsScanned()) { 75 handleScanResults(Arrays.asList(results[0].getResults())); 76 } 77 } 78 79 @Override 80 public void onFullResult(ScanResult fullScanResult) { 81 // no-op 82 } 83 84 @Override 85 public void onSuccess() { 86 // no-op 87 } 88 89 @Override 90 public void onFailure(int reason, String description) { 91 Log.e(TAG, "ScanListener onFailure: " + reason + ": " + description); 92 } 93 }; 94 95 /** Whether this feature is enabled in Settings. */ 96 private boolean mWifiWakeupEnabled; 97 98 /** Whether the WakeupController is currently active. */ 99 private boolean mIsActive = false; 100 101 /** The number of scans that have been handled by the controller since last {@link #reset()}. */ 102 private int mNumScansHandled = 0; 103 104 /** Whether Wifi verbose logging is enabled. */ 105 private boolean mVerboseLoggingEnabled; 106 107 public WakeupController( 108 Context context, 109 Looper looper, 110 WakeupLock wakeupLock, 111 WakeupEvaluator wakeupEvaluator, 112 WakeupOnboarding wakeupOnboarding, 113 WifiConfigManager wifiConfigManager, 114 WifiConfigStore wifiConfigStore, 115 WifiWakeMetrics wifiWakeMetrics, 116 WifiInjector wifiInjector, 117 FrameworkFacade frameworkFacade) { 118 mContext = context; 119 mHandler = new Handler(looper); 120 mWakeupLock = wakeupLock; 121 mWakeupEvaluator = wakeupEvaluator; 122 mWakeupOnboarding = wakeupOnboarding; 123 mWifiConfigManager = wifiConfigManager; 124 mWifiWakeMetrics = wifiWakeMetrics; 125 mFrameworkFacade = frameworkFacade; 126 mWifiInjector = wifiInjector; 127 mContentObserver = new ContentObserver(mHandler) { 128 @Override 129 public void onChange(boolean selfChange) { 130 readWifiWakeupEnabledFromSettings(); 131 mWakeupOnboarding.setOnboarded(); 132 } 133 }; 134 mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor( 135 Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver); 136 readWifiWakeupEnabledFromSettings(); 137 138 // registering the store data here has the effect of reading the persisted value of the 139 // data sources after system boot finishes 140 mWakeupConfigStoreData = new WakeupConfigStoreData( 141 new IsActiveDataSource(), 142 mWakeupOnboarding.getIsOnboadedDataSource(), 143 mWakeupOnboarding.getNotificationsDataSource(), 144 mWakeupLock.getDataSource()); 145 wifiConfigStore.registerStoreData(mWakeupConfigStoreData); 146 } 147 148 private void readWifiWakeupEnabledFromSettings() { 149 mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting( 150 mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1; 151 Log.d(TAG, "WifiWake " + (mWifiWakeupEnabled ? "enabled" : "disabled")); 152 } 153 154 private void setActive(boolean isActive) { 155 if (mIsActive != isActive) { 156 Log.d(TAG, "Setting active to " + isActive); 157 mIsActive = isActive; 158 mWifiConfigManager.saveToStore(false /* forceWrite */); 159 } 160 } 161 162 /** 163 * Starts listening for incoming scans. 164 * 165 * <p>Should only be called upon entering ScanMode. WakeupController registers its listener with 166 * the WifiScanner. If the WakeupController is already active, then it returns early. Otherwise 167 * it performs its initialization steps and sets {@link #mIsActive} to true. 168 */ 169 public void start() { 170 Log.d(TAG, "start()"); 171 mWifiInjector.getWifiScanner().registerScanListener(mScanListener); 172 173 // If already active, we don't want to re-initialize the lock, so return early. 174 if (mIsActive) { 175 // TODO record metric for calls to start() when already active 176 return; 177 } 178 setActive(true); 179 180 // ensure feature is enabled and store data has been read before performing work 181 if (isEnabled()) { 182 mWakeupOnboarding.maybeShowNotification(); 183 184 Set<ScanResultMatchInfo> savedNetworksFromLatestScan = getSavedNetworksFromLatestScan(); 185 if (mVerboseLoggingEnabled) { 186 Log.d(TAG, "Saved networks in most recent scan:" + savedNetworksFromLatestScan); 187 } 188 189 mWifiWakeMetrics.recordStartEvent(savedNetworksFromLatestScan.size()); 190 mWakeupLock.setLock(savedNetworksFromLatestScan); 191 // TODO(b/77291248): request low latency scan here 192 } 193 } 194 195 /** 196 * Stops listening for scans. 197 * 198 * <p>Should only be called upon leaving ScanMode. It deregisters the listener from 199 * WifiScanner. 200 */ 201 public void stop() { 202 Log.d(TAG, "stop()"); 203 mWifiInjector.getWifiScanner().deregisterScanListener(mScanListener); 204 mWakeupOnboarding.onStop(); 205 } 206 207 /** Resets the WakeupController, setting {@link #mIsActive} to false. */ 208 public void reset() { 209 Log.d(TAG, "reset()"); 210 mWifiWakeMetrics.recordResetEvent(mNumScansHandled); 211 mNumScansHandled = 0; 212 setActive(false); 213 } 214 215 /** Sets verbose logging flag based on verbose level. */ 216 public void enableVerboseLogging(int verbose) { 217 mVerboseLoggingEnabled = verbose > 0; 218 mWakeupLock.enableVerboseLogging(mVerboseLoggingEnabled); 219 } 220 221 /** Returns a filtered list of saved networks from the last full scan. */ 222 private Set<ScanResultMatchInfo> getSavedNetworksFromLatestScan() { 223 Set<ScanResult> filteredScanResults = 224 filterScanResults(mWifiInjector.getWifiScanner().getSingleScanResults()); 225 Set<ScanResultMatchInfo> goodMatchInfos = toMatchInfos(filteredScanResults); 226 goodMatchInfos.retainAll(getGoodSavedNetworks()); 227 228 return goodMatchInfos; 229 } 230 231 /** Returns a set of ScanResults with all DFS channels removed. */ 232 private Set<ScanResult> filterScanResults(Collection<ScanResult> scanResults) { 233 int[] dfsChannels = mWifiInjector.getWifiNative() 234 .getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY); 235 if (dfsChannels == null) { 236 dfsChannels = new int[0]; 237 } 238 239 final Set<Integer> dfsChannelSet = Arrays.stream(dfsChannels).boxed() 240 .collect(Collectors.toSet()); 241 242 return scanResults.stream() 243 .filter(scanResult -> !dfsChannelSet.contains(scanResult.frequency)) 244 .collect(Collectors.toSet()); 245 } 246 247 /** Returns a filtered list of saved networks from WifiConfigManager. */ 248 private Set<ScanResultMatchInfo> getGoodSavedNetworks() { 249 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); 250 251 Set<ScanResultMatchInfo> goodSavedNetworks = new HashSet<>(savedNetworks.size()); 252 for (WifiConfiguration config : savedNetworks) { 253 if (isWideAreaNetwork(config) 254 || config.hasNoInternetAccess() 255 || config.noInternetAccessExpected 256 || !config.getNetworkSelectionStatus().getHasEverConnected()) { 257 continue; 258 } 259 goodSavedNetworks.add(ScanResultMatchInfo.fromWifiConfiguration(config)); 260 } 261 262 return goodSavedNetworks; 263 } 264 265 //TODO(b/69271702) implement WAN filtering 266 private static boolean isWideAreaNetwork(WifiConfiguration config) { 267 return false; 268 } 269 270 /** 271 * Handles incoming scan results. 272 * 273 * <p>The controller updates the WakeupLock with the incoming scan results. If WakeupLock is not 274 * yet fully initialized, it adds the current scanResults to the lock and returns. If WakeupLock 275 * is initialized but not empty, the controller updates the lock with the current scan. If it is 276 * both initialized and empty, it evaluates scan results for a match with saved networks. If a 277 * match exists, it enables wifi. 278 * 279 * <p>The feature must be enabled and the store data must be loaded in order for the controller 280 * to handle scan results. 281 * 282 * @param scanResults The scan results with which to update the controller 283 */ 284 private void handleScanResults(Collection<ScanResult> scanResults) { 285 if (!isEnabled()) { 286 Log.d(TAG, "Attempted to handleScanResults while not enabled"); 287 return; 288 } 289 290 // only count scan as handled if isEnabled 291 mNumScansHandled++; 292 if (mVerboseLoggingEnabled) { 293 Log.d(TAG, "Incoming scan #" + mNumScansHandled); 294 } 295 296 // need to show notification here in case user turns phone on while wifi is off 297 mWakeupOnboarding.maybeShowNotification(); 298 299 Set<ScanResult> filteredScanResults = filterScanResults(scanResults); 300 301 mWakeupLock.update(toMatchInfos(filteredScanResults)); 302 if (!mWakeupLock.isUnlocked()) { 303 return; 304 } 305 306 ScanResult network = 307 mWakeupEvaluator.findViableNetwork(filteredScanResults, getGoodSavedNetworks()); 308 309 if (network != null) { 310 Log.d(TAG, "Enabling wifi for network: " + network.SSID); 311 enableWifi(); 312 } 313 } 314 315 /** 316 * Converts ScanResults to ScanResultMatchInfos. 317 */ 318 private static Set<ScanResultMatchInfo> toMatchInfos(Collection<ScanResult> scanResults) { 319 return scanResults.stream() 320 .map(ScanResultMatchInfo::fromScanResult) 321 .collect(Collectors.toSet()); 322 } 323 324 /** 325 * Enables wifi. 326 * 327 * <p>This method ignores all checks and assumes that {@link WifiStateMachine} is currently 328 * in ScanModeState. 329 */ 330 private void enableWifi() { 331 if (USE_PLATFORM_WIFI_WAKE) { 332 // TODO(b/72180295): ensure that there is no race condition with WifiServiceImpl here 333 if (mWifiInjector.getWifiSettingsStore().handleWifiToggled(true /* wifiEnabled */)) { 334 mWifiInjector.getWifiController().sendMessage(CMD_WIFI_TOGGLED); 335 mWifiWakeMetrics.recordWakeupEvent(mNumScansHandled); 336 } 337 } 338 } 339 340 /** 341 * Whether the feature is currently enabled. 342 * 343 * <p>This method checks both the Settings value and the store data to ensure that it has been 344 * read. 345 */ 346 @VisibleForTesting 347 boolean isEnabled() { 348 return mWifiWakeupEnabled && mWakeupConfigStoreData.hasBeenRead(); 349 } 350 351 /** Dumps wakeup controller state. */ 352 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 353 pw.println("Dump of WakeupController"); 354 pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE); 355 pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled); 356 pw.println("isOnboarded: " + mWakeupOnboarding.isOnboarded()); 357 pw.println("configStore hasBeenRead: " + mWakeupConfigStoreData.hasBeenRead()); 358 pw.println("mIsActive: " + mIsActive); 359 pw.println("mNumScansHandled: " + mNumScansHandled); 360 361 mWakeupLock.dump(fd, pw, args); 362 } 363 364 private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> { 365 366 @Override 367 public Boolean getData() { 368 return mIsActive; 369 } 370 371 @Override 372 public void setData(Boolean data) { 373 mIsActive = data; 374 } 375 } 376} 377