• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
WifiWakeupController( Context context, ContentResolver contentResolver, Handler handler, WifiManager wifiManager, PowerManager powerManager, UserManager userManager, WifiWakeupNetworkSelector wifiWakeupNetworkSelector, WifiWakeupHelper wifiWakeupHelper)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}. */
start()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}. */
stop()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 
handlePowerSaverModeChanged()206     private void handlePowerSaverModeChanged() {
207         mPowerSaverModeOn = mPowerManager.isPowerSaveMode();
208         Blog.v(TAG, "handlePowerSaverModeChanged: %b", mPowerSaverModeOn);
209     }
210 
handleWifiApStateChanged()211     private void handleWifiApStateChanged() {
212         mWifiApState = mWifiManager.getWifiApState();
213         Blog.v(TAG, "handleWifiApStateChanged: %d", mWifiApState);
214     }
215 
handleUserRestrictionsChanged()216     private void handleUserRestrictionsChanged() {
217         mWifiConfigRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI);
218         Blog.v(TAG, "handleUserRestrictionsChanged: %b", mWifiConfigRestricted);
219     }
220 
handleConfiguredNetworksChanged()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 
handleWifiStateChanged(boolean calledOnStart)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 
readDisabledSsidsFromSharedPreferences()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 
writeDisabledSsidsToSharedPreferences()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 
handleScanResultsAvailable()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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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 
clear()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