blob: 7354106516eebd42878d628f10652dd677088cb4 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.networkrecommendation;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Handles enabling Wi-Fi for the Wi-Fi Wakeup feature.
*
* <p>
* This class enables Wi-Fi when the user is near a network that would would autojoined if Wi-Fi
* were enabled. When a user disables Wi-Fi, Wi-Fi Wakeup will not enable Wi-Fi until the
* user's context has changed. For saved networks, this context change is defined by the user
* leaving the range of the saved SSIDs that were in range when the user disabled Wi-Fi.
*
* @hide
*/
public class WifiWakeupController {
private static final String TAG = "WifiWakeupController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/** Number of scans to ensure that a previously in range AP is now out of range. */
private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
private final Context mContext;
private final ContentResolver mContentResolver;
private final WifiManager mWifiManager;
private final WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
private final Handler mHandler;
private final AtomicBoolean mStarted;
@VisibleForTesting final ContentObserver mContentObserver;
private final Map<String, WifiConfiguration> mSavedNetworks = new ArrayMap<>();
private final Set<String> mSavedSsidsInLastScan = new ArraySet<>();
private final Set<String> mSavedSsids = new ArraySet<>();
private final Map<String, Integer> mSavedSsidsOnDisable = new ArrayMap<>();
private int mWifiState;
private int mWifiApState;
private boolean mWifiWakeupEnabled;
private boolean mAirplaneModeEnabled;
WifiWakeupController(Context context, ContentResolver contentResolver, Looper looper,
WifiManager wifiManager, WifiWakeupNetworkSelector wifiWakeupNetworkSelector) {
mContext = context;
mContentResolver = contentResolver;
mHandler = new Handler(looper);
mStarted = new AtomicBoolean(false);
mWifiManager = wifiManager;
mWifiWakeupNetworkSelector = wifiWakeupNetworkSelector;
mContentObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
mWifiWakeupEnabled = Settings.Global.getInt(mContentResolver,
Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
mAirplaneModeEnabled = Settings.Global.getInt(mContentResolver,
Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
}
};
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mWifiWakeupEnabled) {
return;
}
if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) {
handleWifiApStateChanged(intent);
} else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
handleWifiStateChanged(intent);
} else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
handleScanResultsAvailable();
} else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(intent.getAction())) {
handleConfiguredNetworksChanged();
}
}
};
/** Starts {@link WifiWakeupController}. */
public void start() {
if (!mStarted.compareAndSet(false, true)) {
return;
}
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
// TODO(b/33695273): conditionally register this receiver based on wifi enabled setting
mContext.registerReceiver(mBroadcastReceiver, filter, null, mHandler);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.AIRPLANE_MODE_ON), true, mContentObserver);
mContentObserver.onChange(true);
handleConfiguredNetworksChanged();
}
/** Stops {@link WifiWakeupController}. */
public void stop() {
if (!mStarted.compareAndSet(true, false)) {
return;
}
mContext.unregisterReceiver(mBroadcastReceiver);
mContentResolver.unregisterContentObserver(mContentObserver);
}
private void handleWifiApStateChanged(Intent intent) {
mWifiApState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
WifiManager.WIFI_AP_STATE_DISABLED);
}
private void handleConfiguredNetworksChanged() {
List<WifiConfiguration> wifiConfigurations = mWifiManager.getConfiguredNetworks();
if (wifiConfigurations == null) {
return;
}
mSavedNetworks.clear();
mSavedSsids.clear();
for (int i = 0; i < wifiConfigurations.size(); i++) {
WifiConfiguration wifiConfiguration = wifiConfigurations.get(i);
if (wifiConfiguration.status != WifiConfiguration.Status.ENABLED
|| wifiConfiguration.ephemeral
|| wifiConfiguration.useExternalScores) {
continue; // Ignore disabled, ephemeral and externally scored networks.
}
if (wifiConfiguration.hasNoInternetAccess()
|| wifiConfiguration.noInternetAccessExpected) {
continue; // Ignore networks that will likely not have internet access.
}
String ssid = WifiInfo.removeDoubleQuotes(wifiConfiguration.SSID);
if (TextUtils.isEmpty(ssid)) {
continue;
}
mSavedNetworks.put(ssid, wifiConfiguration);
mSavedSsids.add(ssid);
}
mSavedSsidsInLastScan.retainAll(mSavedSsids);
}
private void handleWifiStateChanged(Intent intent) {
mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1);
switch (mWifiState) {
case WifiManager.WIFI_STATE_ENABLED:
mSavedSsidsOnDisable.clear();
break;
case WifiManager.WIFI_STATE_DISABLED:
for (String ssid : mSavedSsidsInLastScan) {
mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS);
}
break;
}
}
private void handleScanResultsAvailable() {
List<ScanResult> scanResults = mWifiManager.getScanResults();
mSavedSsidsInLastScan.clear();
for (int i = 0; i < scanResults.size(); i++) {
String ssid = scanResults.get(i).SSID;
if (mSavedSsids.contains(ssid)) {
mSavedSsidsInLastScan.add(ssid);
}
}
if (mAirplaneModeEnabled
|| mWifiState != WifiManager.WIFI_STATE_DISABLED
|| mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED) {
return;
}
// Update mSavedSsidsOnDisable to remove ssids that the user has moved away from.
for (Map.Entry<String, Integer> entry : mSavedSsidsOnDisable.entrySet()) {
if (mSavedSsidsInLastScan.contains(entry.getKey())) {
mSavedSsidsOnDisable.put(entry.getKey(), NUM_SCANS_TO_CONFIRM_AP_LOSS);
} else {
if (entry.getValue() > 1) {
mSavedSsidsOnDisable.put(entry.getKey(), entry.getValue() - 1);
} else {
mSavedSsidsOnDisable.remove(entry.getKey());
}
}
}
if (!mSavedSsidsOnDisable.isEmpty()) {
if (DEBUG) {
Log.d(TAG, "Latest scan result contains ssids from the disabled set: "
+ mSavedSsidsOnDisable);
}
return;
}
WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector.selectNetwork(mSavedNetworks,
scanResults);
if (selectedNetwork != null) {
// TODO(b/33677088): show notification for wifi enablement
if (DEBUG) {
Log.d(TAG, "Enabling wifi for ssid: " + selectedNetwork.SSID);
}
mWifiManager.setWifiEnabled(true /* enabled */);
}
}
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("mStarted " + mStarted.get());
pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
pw.println("mSavedSsids: " + mSavedSsids);
pw.println("mSavedSsidsInLastScan: " + mSavedSsidsInLastScan);
pw.println("mSavedSsidsOnDisable: " + mSavedSsidsOnDisable);
}
}