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