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