WakeupController.java revision aa872c243eb5b27beaefe1360b69a46ceb016b56
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.ArraySet; 30import android.util.Log; 31 32import com.android.internal.annotations.VisibleForTesting; 33 34import java.io.FileDescriptor; 35import java.io.PrintWriter; 36import java.util.Arrays; 37import java.util.Collection; 38import java.util.HashSet; 39import java.util.List; 40import java.util.Set; 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 65 private final WifiScanner.ScanListener mScanListener = new WifiScanner.ScanListener() { 66 @Override 67 public void onPeriodChanged(int periodInMs) { 68 // no-op 69 } 70 71 @Override 72 public void onResults(WifiScanner.ScanData[] results) { 73 if (results.length == 1 && results[0].isAllChannelsScanned()) { 74 handleScanResults(Arrays.asList(results[0].getResults())); 75 } 76 } 77 78 @Override 79 public void onFullResult(ScanResult fullScanResult) { 80 // no-op 81 } 82 83 @Override 84 public void onSuccess() { 85 // no-op 86 } 87 88 @Override 89 public void onFailure(int reason, String description) { 90 Log.e(TAG, "ScanListener onFailure: " + reason + ": " + description); 91 } 92 }; 93 94 /** Whether this feature is enabled in Settings. */ 95 private boolean mWifiWakeupEnabled; 96 97 /** Whether the WakeupController is currently active. */ 98 private boolean mIsActive = false; 99 100 public WakeupController( 101 Context context, 102 Looper looper, 103 WakeupLock wakeupLock, 104 WakeupEvaluator wakeupEvaluator, 105 WakeupOnboarding wakeupOnboarding, 106 WifiConfigManager wifiConfigManager, 107 WifiConfigStore wifiConfigStore, 108 WifiInjector wifiInjector, 109 FrameworkFacade frameworkFacade) { 110 mContext = context; 111 mHandler = new Handler(looper); 112 mWakeupLock = wakeupLock; 113 mWakeupEvaluator = wakeupEvaluator; 114 mWakeupOnboarding = wakeupOnboarding; 115 mWifiConfigManager = wifiConfigManager; 116 mFrameworkFacade = frameworkFacade; 117 mWifiInjector = wifiInjector; 118 mContentObserver = new ContentObserver(mHandler) { 119 @Override 120 public void onChange(boolean selfChange) { 121 mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting( 122 mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1; 123 } 124 }; 125 mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor( 126 Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver); 127 mContentObserver.onChange(false /* selfChange */); 128 129 // registering the store data here has the effect of reading the persisted value of the 130 // data sources after system boot finishes 131 mWakeupConfigStoreData = new WakeupConfigStoreData( 132 new IsActiveDataSource(), 133 mWakeupOnboarding.getDataSource(), 134 mWakeupLock.getDataSource()); 135 wifiConfigStore.registerStoreData(mWakeupConfigStoreData); 136 } 137 138 private void setActive(boolean isActive) { 139 if (mIsActive != isActive) { 140 Log.d(TAG, "Setting active to " + isActive); 141 mIsActive = isActive; 142 mWifiConfigManager.saveToStore(false /* forceWrite */); 143 } 144 } 145 146 /** 147 * Starts listening for incoming scans. 148 * 149 * <p>Should only be called upon entering ScanMode. WakeupController registers its listener with 150 * the WifiScanner. If the WakeupController is already active, then it returns early. Otherwise 151 * it performs its initialization steps and sets {@link #mIsActive} to true. 152 */ 153 public void start() { 154 Log.d(TAG, "start()"); 155 mWifiInjector.getWifiScanner().registerScanListener(mScanListener); 156 157 // If already active, we don't want to re-initialize the lock, so return early. 158 if (mIsActive) { 159 return; 160 } 161 setActive(true); 162 163 // ensure feature is enabled and store data has been read before performing work 164 if (isEnabled()) { 165 mWakeupOnboarding.maybeShowNotification(); 166 mWakeupLock.initialize(getMostRecentSavedScanResults()); 167 } 168 } 169 170 /** 171 * Stops listening for scans. 172 * 173 * <p>Should only be called upon leaving ScanMode. It deregisters the listener from 174 * WifiScanner. 175 */ 176 public void stop() { 177 Log.d(TAG, "stop()"); 178 mWifiInjector.getWifiScanner().deregisterScanListener(mScanListener); 179 mWakeupOnboarding.onStop(); 180 } 181 182 /** Resets the WakeupController, setting {@link #mIsActive} to false. */ 183 public void reset() { 184 Log.d(TAG, "reset()"); 185 setActive(false); 186 } 187 188 /** Returns a list of saved networks from the last full scan. */ 189 private Set<ScanResultMatchInfo> getMostRecentSavedScanResults() { 190 Set<ScanResultMatchInfo> goodSavedNetworks = getGoodSavedNetworks(); 191 192 List<ScanResult> scanResults = mWifiInjector.getWifiScanner().getSingleScanResults(); 193 Set<ScanResultMatchInfo> lastSeenNetworks = new HashSet<>(scanResults.size()); 194 for (ScanResult scanResult : scanResults) { 195 lastSeenNetworks.add(ScanResultMatchInfo.fromScanResult(scanResult)); 196 } 197 198 lastSeenNetworks.retainAll(goodSavedNetworks); 199 return lastSeenNetworks; 200 } 201 202 /** Returns a filtered list of saved networks from WifiConfigManager. */ 203 private Set<ScanResultMatchInfo> getGoodSavedNetworks() { 204 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); 205 206 Set<ScanResultMatchInfo> goodSavedNetworks = new HashSet<>(savedNetworks.size()); 207 for (WifiConfiguration config : savedNetworks) { 208 if (isWideAreaNetwork(config) 209 || config.hasNoInternetAccess() 210 || config.noInternetAccessExpected 211 || !config.getNetworkSelectionStatus().getHasEverConnected()) { 212 continue; 213 } 214 goodSavedNetworks.add(ScanResultMatchInfo.fromWifiConfiguration(config)); 215 } 216 217 Log.d(TAG, "getGoodSavedNetworks: " + goodSavedNetworks.size()); 218 return goodSavedNetworks; 219 } 220 221 //TODO(b/69271702) implement WAN filtering 222 private boolean isWideAreaNetwork(WifiConfiguration wifiConfiguration) { 223 return false; 224 } 225 226 /** 227 * Handles incoming scan results. 228 * 229 * <p>The controller updates the WakeupLock with the incoming scan results. If WakeupLock is 230 * empty, it evaluates scan results for a match with saved networks. If a match exists, it 231 * enables wifi. 232 * 233 * <p>The feature must be enabled and the store data must be loaded in order for the controller 234 * to handle scan results. 235 * 236 * @param scanResults The scan results with which to update the controller 237 */ 238 private void handleScanResults(Collection<ScanResult> scanResults) { 239 if (!isEnabled()) { 240 return; 241 } 242 243 // need to show notification here in case user enables Wifi Wake when Wifi is off 244 mWakeupOnboarding.maybeShowNotification(); 245 if (!mWakeupOnboarding.isOnboarded()) { 246 return; 247 } 248 249 // only update the wakeup lock if it's not already empty 250 if (!mWakeupLock.isEmpty()) { 251 Set<ScanResultMatchInfo> networks = new ArraySet<>(); 252 for (ScanResult scanResult : scanResults) { 253 networks.add(ScanResultMatchInfo.fromScanResult(scanResult)); 254 } 255 mWakeupLock.update(networks); 256 257 // if wakeup lock is still not empty, return 258 if (!mWakeupLock.isEmpty()) { 259 return; 260 } 261 262 Log.d(TAG, "WakeupLock emptied"); 263 } 264 265 ScanResult network = 266 mWakeupEvaluator.findViableNetwork(scanResults, getGoodSavedNetworks()); 267 268 if (network != null) { 269 Log.d(TAG, "Found viable network: " + network.SSID); 270 onNetworkFound(network); 271 } 272 } 273 274 private void onNetworkFound(ScanResult scanResult) { 275 if (isEnabled() && mIsActive && USE_PLATFORM_WIFI_WAKE) { 276 Log.d(TAG, "Enabling wifi for network: " + scanResult.SSID); 277 enableWifi(); 278 } 279 } 280 281 /** 282 * Enables wifi. 283 * 284 * <p>This method ignores all checks and assumes that {@link WifiStateMachine} is currently 285 * in ScanModeState. 286 */ 287 private void enableWifi() { 288 // TODO(b/72180295): ensure that there is no race condition with WifiServiceImpl here 289 if (mWifiInjector.getWifiSettingsStore().handleWifiToggled(true /* wifiEnabled */)) { 290 mWifiInjector.getWifiController().sendMessage(CMD_WIFI_TOGGLED); 291 } 292 } 293 294 /** 295 * Whether the feature is currently enabled. 296 * 297 * <p>This method checks both the Settings value and the store data to ensure that it has been 298 * read. 299 */ 300 @VisibleForTesting 301 boolean isEnabled() { 302 return mWifiWakeupEnabled && mWakeupConfigStoreData.hasBeenRead(); 303 } 304 305 /** Dumps wakeup controller state. */ 306 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 307 pw.println("Dump of WakeupController"); 308 pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled); 309 pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE); 310 pw.println("mIsActive: " + mIsActive); 311 mWakeupLock.dump(fd, pw, args); 312 } 313 314 private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> { 315 316 @Override 317 public Boolean getData() { 318 return mIsActive; 319 } 320 321 @Override 322 public void setData(Boolean data) { 323 mIsActive = data; 324 } 325 } 326} 327