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