Home | History | Annotate | Download | only in wifi
      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