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