Home | History | Annotate | Download | only in wakeup
      1 /*
      2  * Copyright (C) 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 package com.android.networkrecommendation.wakeup;
     17 
     18 import static com.android.networkrecommendation.Constants.TAG;
     19 
     20 import android.content.BroadcastReceiver;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.database.ContentObserver;
     26 import android.net.wifi.ScanResult;
     27 import android.net.wifi.WifiConfiguration;
     28 import android.net.wifi.WifiManager;
     29 import android.os.Handler;
     30 import android.os.PowerManager;
     31 import android.os.UserManager;
     32 import android.provider.Settings;
     33 import android.support.annotation.VisibleForTesting;
     34 import android.text.TextUtils;
     35 import android.util.ArrayMap;
     36 import android.util.ArraySet;
     37 import com.android.networkrecommendation.config.G;
     38 import com.android.networkrecommendation.config.Preferences;
     39 import com.android.networkrecommendation.config.WideAreaNetworks;
     40 import com.android.networkrecommendation.scoring.util.HashUtil;
     41 import com.android.networkrecommendation.util.Blog;
     42 import com.android.networkrecommendation.util.RoboCompatUtil;
     43 import com.android.networkrecommendation.util.WifiConfigurationUtil;
     44 import java.io.FileDescriptor;
     45 import java.io.PrintWriter;
     46 import java.util.List;
     47 import java.util.Map;
     48 import java.util.Set;
     49 import java.util.concurrent.atomic.AtomicBoolean;
     50 
     51 /**
     52  * Handles enabling Wi-Fi for the Wi-Fi Wakeup feature.
     53  *
     54  * <p>This class enables Wi-Fi when the user is near a network that would would autojoined if Wi-Fi
     55  * were enabled. When a user disables Wi-Fi, Wi-Fi Wakeup will not enable Wi-Fi until the user's
     56  * context has changed. For saved networks, this context change is defined by the user leaving the
     57  * range of the saved SSIDs that were in range when the user disabled Wi-Fi.
     58  *
     59  * @hide
     60  */
     61 public class WifiWakeupController {
     62     /** Number of scans to ensure that a previously in range AP is now out of range. */
     63     private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
     64 
     65     private final Context mContext;
     66     private final ContentResolver mContentResolver;
     67     private final WifiManager mWifiManager;
     68     private final PowerManager mPowerManager;
     69     private final UserManager mUserManager;
     70     private final WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
     71     private final Handler mHandler;
     72     private final WifiWakeupHelper mWifiWakeupHelper;
     73     private final AtomicBoolean mStarted;
     74     @VisibleForTesting final ContentObserver mContentObserver;
     75 
     76     private final Map<String, WifiConfiguration> mSavedNetworks = new ArrayMap<>();
     77     private final Set<String> mSavedSsidsInLastScan = new ArraySet<>();
     78     private final Set<String> mSavedSsids = new ArraySet<>();
     79     private final Map<String, Integer> mSavedSsidsOnDisable = new ArrayMap<>();
     80     private final SavedNetworkCounts mSavedNetworkCounts = new SavedNetworkCounts();
     81     private int mWifiState;
     82     private int mWifiApState;
     83     private boolean mWifiWakeupEnabled;
     84     private boolean mAirplaneModeEnabled;
     85     private boolean mAutopilotEnabledWifi;
     86     private boolean mPowerSaverModeOn;
     87     private boolean mWifiConfigRestricted;
     88 
     89     public WifiWakeupController(
     90             Context context,
     91             ContentResolver contentResolver,
     92             Handler handler,
     93             WifiManager wifiManager,
     94             PowerManager powerManager,
     95             UserManager userManager,
     96             WifiWakeupNetworkSelector wifiWakeupNetworkSelector,
     97             WifiWakeupHelper wifiWakeupHelper) {
     98         mContext = context;
     99         mContentResolver = contentResolver;
    100         mHandler = handler;
    101         mWifiWakeupHelper = wifiWakeupHelper;
    102         mStarted = new AtomicBoolean(false);
    103         mWifiManager = wifiManager;
    104         mPowerManager = powerManager;
    105         mUserManager = userManager;
    106         mWifiWakeupNetworkSelector = wifiWakeupNetworkSelector;
    107         mContentObserver =
    108                 new ContentObserver(mHandler) {
    109                     @Override
    110                     public void onChange(boolean selfChange) {
    111                         mWifiWakeupEnabled =
    112                                 Settings.Global.getInt(
    113                                                 mContentResolver,
    114                                                 Settings.Global.WIFI_WAKEUP_ENABLED,
    115                                                 0)
    116                                         == 1;
    117                         mAirplaneModeEnabled =
    118                                 Settings.Global.getInt(
    119                                                 mContentResolver,
    120                                                 Settings.Global.AIRPLANE_MODE_ON,
    121                                                 0)
    122                                         == 1;
    123                         Blog.d(
    124                                 TAG,
    125                                 "onChange: [mWifiWakeupEnabled=%b,mAirplaneModeEnabled=%b]",
    126                                 mWifiWakeupEnabled,
    127                                 mAirplaneModeEnabled);
    128                     }
    129                 };
    130     }
    131 
    132     private final BroadcastReceiver mBroadcastReceiver =
    133             new BroadcastReceiver() {
    134                 @Override
    135                 public void onReceive(Context context, Intent intent) {
    136                     try {
    137                         if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) {
    138                             handleWifiApStateChanged();
    139                         } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(
    140                                 intent.getAction())) {
    141                             handleWifiStateChanged(false);
    142                         } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(
    143                                 intent.getAction())) {
    144                             handleScanResultsAvailable();
    145                         } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(
    146                                 intent.getAction())) {
    147                             handleConfiguredNetworksChanged();
    148                         } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(
    149                                 intent.getAction())) {
    150                             handlePowerSaverModeChanged();
    151                         } else if (RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED.equals(
    152                                 intent.getAction())) {
    153                             handleUserRestrictionsChanged();
    154                         }
    155                     } catch (RuntimeException re) {
    156                         // TODO(b/35044022) Remove try/catch after a couple of releases when we are confident
    157                         // this is not going to throw.
    158                         Blog.e(TAG, re, "RuntimeException in broadcast receiver.");
    159                     }
    160                 }
    161             };
    162 
    163     /** Starts {@link WifiWakeupController}. */
    164     public void start() {
    165         if (!mStarted.compareAndSet(false, true)) {
    166             return;
    167         }
    168         Blog.d(TAG, "Starting WifiWakeupController.");
    169 
    170         IntentFilter filter = new IntentFilter();
    171         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    172         filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    173         filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
    174         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
    175         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
    176         filter.addAction(RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED);
    177         // TODO(b/33695273): conditionally register this receiver based on wifi enabled setting
    178         mContext.registerReceiver(mBroadcastReceiver, filter, null, mHandler);
    179         mContentResolver.registerContentObserver(
    180                 Settings.Global.getUriFor(Settings.Global.WIFI_WAKEUP_ENABLED),
    181                 true,
    182                 mContentObserver);
    183         mContentResolver.registerContentObserver(
    184                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
    185                 true,
    186                 mContentObserver);
    187         mContentObserver.onChange(true);
    188         handlePowerSaverModeChanged();
    189         handleUserRestrictionsChanged();
    190         handleWifiApStateChanged();
    191         handleConfiguredNetworksChanged();
    192         handleWifiStateChanged(true);
    193         handleScanResultsAvailable();
    194     }
    195 
    196     /** Stops {@link WifiWakeupController}. */
    197     public void stop() {
    198         if (!mStarted.compareAndSet(true, false)) {
    199             return;
    200         }
    201         Blog.d(TAG, "Stopping WifiWakeupController.");
    202         mContext.unregisterReceiver(mBroadcastReceiver);
    203         mContentResolver.unregisterContentObserver(mContentObserver);
    204     }
    205 
    206     private void handlePowerSaverModeChanged() {
    207         mPowerSaverModeOn = mPowerManager.isPowerSaveMode();
    208         Blog.v(TAG, "handlePowerSaverModeChanged: %b", mPowerSaverModeOn);
    209     }
    210 
    211     private void handleWifiApStateChanged() {
    212         mWifiApState = mWifiManager.getWifiApState();
    213         Blog.v(TAG, "handleWifiApStateChanged: %d", mWifiApState);
    214     }
    215 
    216     private void handleUserRestrictionsChanged() {
    217         mWifiConfigRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI);
    218         Blog.v(TAG, "handleUserRestrictionsChanged: %b", mWifiConfigRestricted);
    219     }
    220 
    221     private void handleConfiguredNetworksChanged() {
    222         List<WifiConfiguration> wifiConfigurations = mWifiManager.getConfiguredNetworks();
    223         if (wifiConfigurations == null) {
    224             return;
    225         }
    226         Blog.v(TAG, "handleConfiguredNetworksChanged: %d", wifiConfigurations.size());
    227 
    228         mSavedNetworkCounts.clear();
    229         mSavedNetworkCounts.total = wifiConfigurations.size();
    230         mSavedNetworks.clear();
    231         mSavedSsids.clear();
    232         for (int i = 0; i < wifiConfigurations.size(); i++) {
    233             WifiConfiguration wifiConfiguration = wifiConfigurations.get(i);
    234             if (wifiConfiguration.status != WifiConfiguration.Status.ENABLED
    235                     && wifiConfiguration.status != WifiConfiguration.Status.CURRENT) {
    236                 continue; // Ignore networks that are not connected or enabled.
    237             }
    238             mSavedNetworkCounts.enabled++;
    239             if (RoboCompatUtil.getInstance().hasNoInternetAccess(wifiConfiguration)) {
    240                 mSavedNetworkCounts.noInternetAccess++;
    241                 continue; // Ignore networks that do not have verified internet access.
    242             }
    243             if (RoboCompatUtil.getInstance().isNoInternetAccessExpected(wifiConfiguration)) {
    244                 mSavedNetworkCounts.noInternetAccessExpected++;
    245                 continue; // Ignore networks that are expected not to have internet access.
    246             }
    247             String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration);
    248             if (TextUtils.isEmpty(ssid)) {
    249                 continue;
    250             }
    251             if (WideAreaNetworks.contains(ssid)) {
    252                 mSavedNetworkCounts.blacklisted++;
    253                 continue; // Ignore wide area networks.
    254             }
    255             mSavedNetworks.put(ssid, wifiConfiguration);
    256             mSavedSsids.add(ssid);
    257 
    258             if (WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)) {
    259                 mSavedNetworkCounts.open++;
    260             }
    261             if (RoboCompatUtil.getInstance().useExternalScores(wifiConfiguration)) {
    262                 mSavedNetworkCounts.useExternalScores++;
    263             }
    264         }
    265         mSavedSsidsInLastScan.retainAll(mSavedSsids);
    266     }
    267 
    268     private void handleWifiStateChanged(boolean calledOnStart) {
    269         mWifiState = mWifiManager.getWifiState();
    270         Blog.v(TAG, "handleWifiStateChanged: %d", mWifiState);
    271 
    272         switch (mWifiState) {
    273             case WifiManager.WIFI_STATE_ENABLED:
    274                 mSavedSsidsOnDisable.clear();
    275                 if (!mAutopilotEnabledWifi) {}
    276                 break;
    277             case WifiManager.WIFI_STATE_DISABLED:
    278                 if (calledOnStart) {
    279                     readDisabledSsidsFromSharedPreferences();
    280                 } else {
    281                     for (String ssid : mSavedSsidsInLastScan) {
    282                         mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS);
    283                     }
    284                     writeDisabledSsidsToSharedPreferences();
    285                 }
    286                 Blog.d(TAG, "Disabled ssid set: %s", mSavedSsidsOnDisable);
    287 
    288                 mAutopilotEnabledWifi = false;
    289                 break;
    290             default: // Only handle ENABLED and DISABLED states
    291         }
    292     }
    293 
    294     private void readDisabledSsidsFromSharedPreferences() {
    295         Set<String> ssidsOnDisable = Preferences.savedSsidsOnDisable.get();
    296         for (String ssid : mSavedSsids) {
    297             if (ssidsOnDisable.contains(HashUtil.getSsidHash(ssid))) {
    298                 mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS);
    299             }
    300         }
    301     }
    302 
    303     private void writeDisabledSsidsToSharedPreferences() {
    304         Set<String> ssids = new ArraySet<>();
    305         for (String ssid : mSavedSsidsOnDisable.keySet()) {
    306             ssids.add(HashUtil.getSsidHash(ssid));
    307         }
    308         Preferences.savedSsidsOnDisable.put(ssids);
    309     }
    310 
    311     private void handleScanResultsAvailable() {
    312         if (!mWifiWakeupEnabled || mWifiConfigRestricted) {
    313             return;
    314         }
    315         List<ScanResult> scanResults = mWifiManager.getScanResults();
    316         if (scanResults == null) {
    317             return;
    318         }
    319         Blog.v(TAG, "handleScanResultsAvailable: %d", scanResults.size());
    320 
    321         mSavedSsidsInLastScan.clear();
    322         for (int i = 0; i < scanResults.size(); i++) {
    323             String ssid = scanResults.get(i).SSID;
    324             if (mSavedSsids.contains(ssid)) {
    325                 mSavedSsidsInLastScan.add(ssid);
    326             }
    327         }
    328 
    329         if (mAirplaneModeEnabled
    330                 || mWifiState != WifiManager.WIFI_STATE_DISABLED
    331                 || mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED
    332                 || mPowerSaverModeOn) {
    333             return;
    334         }
    335 
    336         // Update mSavedSsidsOnDisable to remove ssids that the user has moved away from.
    337         for (Map.Entry<String, Integer> entry : mSavedSsidsOnDisable.entrySet()) {
    338             if (mSavedSsidsInLastScan.contains(entry.getKey())) {
    339                 mSavedSsidsOnDisable.put(entry.getKey(), NUM_SCANS_TO_CONFIRM_AP_LOSS);
    340             } else {
    341                 if (entry.getValue() > 1) {
    342                     mSavedSsidsOnDisable.put(entry.getKey(), entry.getValue() - 1);
    343                 } else {
    344                     mSavedSsidsOnDisable.remove(entry.getKey());
    345                 }
    346             }
    347         }
    348 
    349         if (!mSavedSsidsOnDisable.isEmpty()) {
    350             Blog.d(
    351                     TAG,
    352                     "Scan results contain ssids from the disabled set: %s",
    353                     mSavedSsidsOnDisable);
    354             return;
    355         }
    356 
    357         if (mSavedSsidsInLastScan.isEmpty()) {
    358             Blog.v(TAG, "Scan results do not contain any saved ssids.");
    359             return;
    360         }
    361 
    362         WifiConfiguration selectedNetwork =
    363                 mWifiWakeupNetworkSelector.selectNetwork(mSavedNetworks, scanResults);
    364         if (selectedNetwork != null) {
    365             Blog.d(
    366                     TAG,
    367                     "Enabling wifi for ssid: %s",
    368                     Blog.pii(selectedNetwork.SSID, G.Netrec.enableSensitiveLogging.get()));
    369 
    370             mAutopilotEnabledWifi = true;
    371             mWifiManager.setWifiEnabled(true /* enabled */);
    372             mWifiWakeupHelper.startWifiSession(selectedNetwork);
    373         }
    374     }
    375 
    376     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    377         pw.println("mStarted " + mStarted.get());
    378         pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
    379         pw.println("mSavedSsids: " + mSavedSsids);
    380         pw.println("mSavedSsidsInLastScan: " + mSavedSsidsInLastScan);
    381         pw.println("mSavedSsidsOnDisable: " + mSavedSsidsOnDisable);
    382     }
    383 
    384     /** Class to track counts for saved networks for logging. */
    385     private static class SavedNetworkCounts {
    386         int total;
    387         int open;
    388         int enabled;
    389         int noInternetAccess;
    390         int noInternetAccessExpected;
    391         int useExternalScores;
    392         int blacklisted;
    393 
    394         void clear() {
    395             total = 0;
    396             open = 0;
    397             enabled = 0;
    398             noInternetAccess = 0;
    399             noInternetAccessExpected = 0;
    400             useExternalScores = 0;
    401             blacklisted = 0;
    402         }
    403     }
    404 }
    405