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 static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED; 20 21 import android.content.Context; 22 import android.database.ContentObserver; 23 import android.net.wifi.ScanResult; 24 import android.net.wifi.WifiConfiguration; 25 import android.net.wifi.WifiScanner; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.provider.Settings; 29 import android.util.Log; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.io.FileDescriptor; 34 import java.io.PrintWriter; 35 import java.util.Arrays; 36 import java.util.Collection; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Set; 40 import 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 */ 48 public 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(filterDfsScanResults(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 restart the session, so return early. 174 if (mIsActive) { 175 mWifiWakeMetrics.recordIgnoredStart(); 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 List<ScanResult> scanResults = 185 filterDfsScanResults(mWifiInjector.getWifiScanner().getSingleScanResults()); 186 Set<ScanResultMatchInfo> matchInfos = toMatchInfos(scanResults); 187 matchInfos.retainAll(getGoodSavedNetworks()); 188 189 if (mVerboseLoggingEnabled) { 190 Log.d(TAG, "Saved networks in most recent scan:" + matchInfos); 191 } 192 193 mWifiWakeMetrics.recordStartEvent(matchInfos.size()); 194 mWakeupLock.setLock(matchInfos); 195 // TODO(b/77291248): request low latency scan here 196 } 197 } 198 199 /** 200 * Stops listening for scans. 201 * 202 * <p>Should only be called upon leaving ScanMode. It deregisters the listener from 203 * WifiScanner. 204 */ 205 public void stop() { 206 Log.d(TAG, "stop()"); 207 mWifiInjector.getWifiScanner().deregisterScanListener(mScanListener); 208 mWakeupOnboarding.onStop(); 209 } 210 211 /** Resets the WakeupController, setting {@link #mIsActive} to false. */ 212 public void reset() { 213 Log.d(TAG, "reset()"); 214 mWifiWakeMetrics.recordResetEvent(mNumScansHandled); 215 mNumScansHandled = 0; 216 setActive(false); 217 } 218 219 /** Sets verbose logging flag based on verbose level. */ 220 public void enableVerboseLogging(int verbose) { 221 mVerboseLoggingEnabled = verbose > 0; 222 mWakeupLock.enableVerboseLogging(mVerboseLoggingEnabled); 223 } 224 225 /** Returns a list of ScanResults with DFS channels removed. */ 226 private List<ScanResult> filterDfsScanResults(Collection<ScanResult> scanResults) { 227 int[] dfsChannels = mWifiInjector.getWifiNative() 228 .getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY); 229 if (dfsChannels == null) { 230 dfsChannels = new int[0]; 231 } 232 233 final Set<Integer> dfsChannelSet = Arrays.stream(dfsChannels).boxed() 234 .collect(Collectors.toSet()); 235 236 return scanResults.stream() 237 .filter(scanResult -> !dfsChannelSet.contains(scanResult.frequency)) 238 .collect(Collectors.toList()); 239 } 240 241 /** Returns a filtered set of saved networks from WifiConfigManager. */ 242 private Set<ScanResultMatchInfo> getGoodSavedNetworks() { 243 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); 244 245 Set<ScanResultMatchInfo> goodSavedNetworks = new HashSet<>(savedNetworks.size()); 246 for (WifiConfiguration config : savedNetworks) { 247 if (isWideAreaNetwork(config) 248 || config.hasNoInternetAccess() 249 || config.noInternetAccessExpected 250 || !config.getNetworkSelectionStatus().getHasEverConnected()) { 251 continue; 252 } 253 goodSavedNetworks.add(ScanResultMatchInfo.fromWifiConfiguration(config)); 254 } 255 256 return goodSavedNetworks; 257 } 258 259 //TODO(b/69271702) implement WAN filtering 260 private static boolean isWideAreaNetwork(WifiConfiguration config) { 261 return false; 262 } 263 264 /** 265 * Handles incoming scan results. 266 * 267 * <p>The controller updates the WakeupLock with the incoming scan results. If WakeupLock is not 268 * yet fully initialized, it adds the current scanResults to the lock and returns. If WakeupLock 269 * is initialized but not empty, the controller updates the lock with the current scan. If it is 270 * both initialized and empty, it evaluates scan results for a match with saved networks. If a 271 * match exists, it enables wifi. 272 * 273 * <p>The feature must be enabled and the store data must be loaded in order for the controller 274 * to handle scan results. 275 * 276 * @param scanResults The scan results with which to update the controller 277 */ 278 private void handleScanResults(Collection<ScanResult> scanResults) { 279 if (!isEnabled()) { 280 Log.d(TAG, "Attempted to handleScanResults while not enabled"); 281 return; 282 } 283 284 // only count scan as handled if isEnabled 285 mNumScansHandled++; 286 if (mVerboseLoggingEnabled) { 287 Log.d(TAG, "Incoming scan #" + mNumScansHandled); 288 } 289 290 // need to show notification here in case user turns phone on while wifi is off 291 mWakeupOnboarding.maybeShowNotification(); 292 293 // filter out unsaved networks 294 Set<ScanResultMatchInfo> goodSavedNetworks = getGoodSavedNetworks(); 295 Set<ScanResultMatchInfo> matchInfos = toMatchInfos(scanResults); 296 matchInfos.retainAll(goodSavedNetworks); 297 298 mWakeupLock.update(matchInfos); 299 if (!mWakeupLock.isUnlocked()) { 300 return; 301 } 302 303 ScanResult network = mWakeupEvaluator.findViableNetwork(scanResults, goodSavedNetworks); 304 305 if (network != null) { 306 Log.d(TAG, "Enabling wifi for network: " + network.SSID); 307 enableWifi(); 308 } 309 } 310 311 /** 312 * Converts ScanResults to ScanResultMatchInfos. 313 */ 314 private static Set<ScanResultMatchInfo> toMatchInfos(Collection<ScanResult> scanResults) { 315 return scanResults.stream() 316 .map(ScanResultMatchInfo::fromScanResult) 317 .collect(Collectors.toSet()); 318 } 319 320 /** 321 * Enables wifi. 322 * 323 * <p>This method ignores all checks and assumes that {@link WifiStateMachine} is currently 324 * in ScanModeState. 325 */ 326 private void enableWifi() { 327 if (USE_PLATFORM_WIFI_WAKE) { 328 // TODO(b/72180295): ensure that there is no race condition with WifiServiceImpl here 329 if (mWifiInjector.getWifiSettingsStore().handleWifiToggled(true /* wifiEnabled */)) { 330 mWifiInjector.getWifiController().sendMessage(CMD_WIFI_TOGGLED); 331 mWifiWakeMetrics.recordWakeupEvent(mNumScansHandled); 332 } 333 } 334 } 335 336 /** 337 * Whether the feature is currently enabled. 338 * 339 * <p>This method checks both the Settings value and the store data to ensure that it has been 340 * read. 341 */ 342 @VisibleForTesting 343 boolean isEnabled() { 344 return mWifiWakeupEnabled && mWakeupConfigStoreData.hasBeenRead(); 345 } 346 347 /** Dumps wakeup controller state. */ 348 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 349 pw.println("Dump of WakeupController"); 350 pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE); 351 pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled); 352 pw.println("isOnboarded: " + mWakeupOnboarding.isOnboarded()); 353 pw.println("configStore hasBeenRead: " + mWakeupConfigStoreData.hasBeenRead()); 354 pw.println("mIsActive: " + mIsActive); 355 pw.println("mNumScansHandled: " + mNumScansHandled); 356 357 mWakeupLock.dump(fd, pw, args); 358 } 359 360 private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> { 361 362 @Override 363 public Boolean getData() { 364 return mIsActive; 365 } 366 367 @Override 368 public void setData(Boolean data) { 369 mIsActive = data; 370 } 371 } 372 } 373