blob: e3bd207dfb85e79ccb2ad651bbbf20635dc8c783 [file] [log] [blame]
/*
* Copyright (C) 2015 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.server.wifi.scanner;
import android.app.AlarmManager;
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiScanner;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.internal.R;
import com.android.server.wifi.Clock;
import com.android.server.wifi.ScanDetail;
import com.android.server.wifi.WifiMonitor;
import com.android.server.wifi.WifiNative;
import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
import com.android.server.wifi.util.ScanResultUtil;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.concurrent.GuardedBy;
/**
* Implementation of the WifiScanner HAL API that uses wificond to perform all scans
* @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method.
*/
public class WificondScannerImpl extends WifiScannerImpl implements Handler.Callback {
private static final String TAG = "WificondScannerImpl";
private static final boolean DBG = false;
public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
// Max number of networks that can be specified to wificond per scan request
public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
private static final int SCAN_BUFFER_CAPACITY = 10;
private static final int MAX_APS_PER_SCAN = 32;
private static final int MAX_SCAN_BUCKETS = 16;
private final Context mContext;
private final String mIfaceName;
private final WifiNative mWifiNative;
private final WifiMonitor mWifiMonitor;
private final AlarmManager mAlarmManager;
private final Handler mEventHandler;
private final ChannelHelper mChannelHelper;
private final Clock mClock;
private final Object mSettingsLock = new Object();
private ArrayList<ScanDetail> mNativeScanResults;
private ArrayList<ScanDetail> mNativePnoScanResults;
private WifiScanner.ScanData mLatestSingleScanResult =
new WifiScanner.ScanData(0, 0, new ScanResult[0]);
// Settings for the currently running single scan, null if no scan active
private LastScanSettings mLastScanSettings = null;
// Settings for the currently running pno scan, null if no scan active
private LastPnoScanSettings mLastPnoScanSettings = null;
private final boolean mHwPnoScanSupported;
/**
* Duration to wait before timing out a scan.
*
* The expected behavior is that the hardware will return a failed scan if it does not
* complete, but timeout just in case it does not.
*/
private static final long SCAN_TIMEOUT_MS = 15000;
@GuardedBy("mSettingsLock")
private AlarmManager.OnAlarmListener mScanTimeoutListener;
public WificondScannerImpl(Context context, String ifaceName, WifiNative wifiNative,
WifiMonitor wifiMonitor, ChannelHelper channelHelper,
Looper looper, Clock clock) {
mContext = context;
mIfaceName = ifaceName;
mWifiNative = wifiNative;
mWifiMonitor = wifiMonitor;
mChannelHelper = channelHelper;
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
mEventHandler = new Handler(looper, this);
mClock = clock;
// Check if the device supports HW PNO scans.
mHwPnoScanSupported = mContext.getResources().getBoolean(
R.bool.config_wifi_background_scan_support);
wifiMonitor.registerHandler(mIfaceName,
WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
wifiMonitor.registerHandler(mIfaceName,
WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
wifiMonitor.registerHandler(mIfaceName,
WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
}
@Override
public void cleanup() {
synchronized (mSettingsLock) {
stopHwPnoScan();
mLastScanSettings = null; // finally clear any active scan
mLastPnoScanSettings = null; // finally clear any active scan
mWifiMonitor.deregisterHandler(mIfaceName,
WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
mWifiMonitor.deregisterHandler(mIfaceName,
WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
mWifiMonitor.deregisterHandler(mIfaceName,
WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
}
}
@Override
public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) {
capabilities.max_scan_cache_size = Integer.MAX_VALUE;
capabilities.max_scan_buckets = MAX_SCAN_BUCKETS;
capabilities.max_ap_cache_per_scan = MAX_APS_PER_SCAN;
capabilities.max_rssi_sample_size = 8;
capabilities.max_scan_reporting_threshold = SCAN_BUFFER_CAPACITY;
return true;
}
@Override
public ChannelHelper getChannelHelper() {
return mChannelHelper;
}
@Override
public boolean startSingleScan(WifiNative.ScanSettings settings,
WifiNative.ScanEventHandler eventHandler) {
if (eventHandler == null || settings == null) {
Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings
+ ",eventHandler=" + eventHandler);
return false;
}
synchronized (mSettingsLock) {
if (mLastScanSettings != null) {
Log.w(TAG, "A single scan is already running");
return false;
}
ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
boolean reportFullResults = false;
for (int i = 0; i < settings.num_buckets; ++i) {
WifiNative.BucketSettings bucketSettings = settings.buckets[i];
if ((bucketSettings.report_events
& WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
reportFullResults = true;
}
allFreqs.addChannels(bucketSettings);
}
List<String> hiddenNetworkSSIDSet = new ArrayList<>();
if (settings.hiddenNetworks != null) {
int numHiddenNetworks =
Math.min(settings.hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
for (int i = 0; i < numHiddenNetworks; i++) {
hiddenNetworkSSIDSet.add(settings.hiddenNetworks[i].ssid);
}
}
mLastScanSettings = new LastScanSettings(
mClock.getElapsedSinceBootMillis(),
reportFullResults, allFreqs, eventHandler);
boolean success = false;
Set<Integer> freqs;
if (!allFreqs.isEmpty()) {
freqs = allFreqs.getScanFreqs();
success = mWifiNative.scan(
mIfaceName, settings.scanType, freqs, hiddenNetworkSSIDSet);
if (!success) {
Log.e(TAG, "Failed to start scan, freqs=" + freqs);
}
} else {
// There is a scan request but no available channels could be scanned for.
// We regard it as a scan failure in this case.
Log.e(TAG, "Failed to start scan because there is no available channel to scan");
}
if (success) {
if (DBG) {
Log.d(TAG, "Starting wifi scan for freqs=" + freqs);
}
mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
@Override public void onAlarm() {
handleScanTimeout();
}
};
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
} else {
// indicate scan failure async
mEventHandler.post(new Runnable() {
@Override public void run() {
reportScanFailure();
}
});
}
return true;
}
}
@Override
public WifiScanner.ScanData getLatestSingleScanResults() {
return mLatestSingleScanResult;
}
@Override
public boolean startBatchedScan(WifiNative.ScanSettings settings,
WifiNative.ScanEventHandler eventHandler) {
Log.w(TAG, "startBatchedScan() is not supported");
return false;
}
@Override
public void stopBatchedScan() {
Log.w(TAG, "stopBatchedScan() is not supported");
}
@Override
public void pauseBatchedScan() {
Log.w(TAG, "pauseBatchedScan() is not supported");
}
@Override
public void restartBatchedScan() {
Log.w(TAG, "restartBatchedScan() is not supported");
}
private void handleScanTimeout() {
synchronized (mSettingsLock) {
Log.e(TAG, "Timed out waiting for scan result from wificond");
reportScanFailure();
mScanTimeoutListener = null;
}
}
@Override
public boolean handleMessage(Message msg) {
switch(msg.what) {
case WifiMonitor.SCAN_FAILED_EVENT:
Log.w(TAG, "Scan failed");
cancelScanTimeout();
reportScanFailure();
break;
case WifiMonitor.PNO_SCAN_RESULTS_EVENT:
pollLatestScanDataForPno();
break;
case WifiMonitor.SCAN_RESULTS_EVENT:
cancelScanTimeout();
pollLatestScanData();
break;
default:
// ignore unknown event
}
return true;
}
private void cancelScanTimeout() {
synchronized (mSettingsLock) {
if (mScanTimeoutListener != null) {
mAlarmManager.cancel(mScanTimeoutListener);
mScanTimeoutListener = null;
}
}
}
private void reportScanFailure() {
synchronized (mSettingsLock) {
if (mLastScanSettings != null) {
if (mLastScanSettings.singleScanEventHandler != null) {
mLastScanSettings.singleScanEventHandler
.onScanStatus(WifiNative.WIFI_SCAN_FAILED);
}
mLastScanSettings = null;
}
}
}
private void reportPnoScanFailure() {
synchronized (mSettingsLock) {
if (mLastPnoScanSettings != null) {
if (mLastPnoScanSettings.pnoScanEventHandler != null) {
mLastPnoScanSettings.pnoScanEventHandler.onPnoScanFailed();
}
// Clean up PNO state, we don't want to continue PNO scanning.
mLastPnoScanSettings = null;
}
}
}
private void pollLatestScanDataForPno() {
synchronized (mSettingsLock) {
if (mLastPnoScanSettings == null) {
// got a scan before we started scanning or after scan was canceled
return;
}
mNativePnoScanResults = mWifiNative.getPnoScanResults(mIfaceName);
List<ScanResult> hwPnoScanResults = new ArrayList<>();
int numFilteredScanResults = 0;
for (int i = 0; i < mNativePnoScanResults.size(); ++i) {
ScanResult result = mNativePnoScanResults.get(i).getScanResult();
long timestamp_ms = result.timestamp / 1000; // convert us -> ms
if (timestamp_ms > mLastPnoScanSettings.startTime) {
hwPnoScanResults.add(result);
} else {
numFilteredScanResults++;
}
}
if (numFilteredScanResults != 0) {
Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results.");
}
if (mLastPnoScanSettings.pnoScanEventHandler != null) {
ScanResult[] pnoScanResultsArray =
hwPnoScanResults.toArray(new ScanResult[hwPnoScanResults.size()]);
mLastPnoScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
}
}
}
/**
* Return one of the WIFI_BAND_# values that was scanned for in this scan.
*/
private static int getBandScanned(ChannelCollection channelCollection) {
if (channelCollection.containsBand(WifiScanner.WIFI_BAND_BOTH_WITH_DFS)) {
return WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
} else if (channelCollection.containsBand(WifiScanner.WIFI_BAND_BOTH)) {
return WifiScanner.WIFI_BAND_BOTH;
} else if (channelCollection.containsBand(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS)) {
return WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS;
} else if (channelCollection.containsBand(WifiScanner.WIFI_BAND_5_GHZ)) {
return WifiScanner.WIFI_BAND_5_GHZ;
} else if (channelCollection.containsBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY)) {
return WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY;
} else if (channelCollection.containsBand(WifiScanner.WIFI_BAND_24_GHZ)) {
return WifiScanner.WIFI_BAND_24_GHZ;
}
return WifiScanner.WIFI_BAND_UNSPECIFIED;
}
private void pollLatestScanData() {
synchronized (mSettingsLock) {
if (mLastScanSettings == null) {
// got a scan before we started scanning or after scan was canceled
return;
}
mNativeScanResults = mWifiNative.getScanResults(mIfaceName);
List<ScanResult> singleScanResults = new ArrayList<>();
int numFilteredScanResults = 0;
for (int i = 0; i < mNativeScanResults.size(); ++i) {
ScanResult result = mNativeScanResults.get(i).getScanResult();
long timestamp_ms = result.timestamp / 1000; // convert us -> ms
if (timestamp_ms > mLastScanSettings.startTime) {
if (mLastScanSettings.singleScanFreqs.containsChannel(
result.frequency)) {
singleScanResults.add(result);
}
} else {
numFilteredScanResults++;
}
}
if (numFilteredScanResults != 0) {
Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
}
if (mLastScanSettings.singleScanEventHandler != null) {
if (mLastScanSettings.reportSingleScanFullResults) {
for (ScanResult scanResult : singleScanResults) {
// ignore buckets scanned since there is only one bucket for a single scan
mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult,
/* bucketsScanned */ 0);
}
}
Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
getBandScanned(mLastScanSettings.singleScanFreqs),
singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
mLastScanSettings.singleScanEventHandler
.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
}
mLastScanSettings = null;
}
}
@Override
public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
return null;
}
private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
return mWifiNative.startPnoScan(mIfaceName, pnoSettings);
}
private void stopHwPnoScan() {
mWifiNative.stopPnoScan(mIfaceName);
}
/**
* Hw Pno Scan is required only for disconnected PNO when the device supports it.
* @param isConnectedPno Whether this is connected PNO vs disconnected PNO.
* @return true if HW PNO scan is required, false otherwise.
*/
private boolean isHwPnoScanRequired(boolean isConnectedPno) {
return (!isConnectedPno && mHwPnoScanSupported);
}
@Override
public boolean setHwPnoList(WifiNative.PnoSettings settings,
WifiNative.PnoEventHandler eventHandler) {
synchronized (mSettingsLock) {
if (mLastPnoScanSettings != null) {
Log.w(TAG, "Already running a PNO scan");
return false;
}
if (!isHwPnoScanRequired(settings.isConnected)) {
return false;
}
if (startHwPnoScan(settings)) {
mLastPnoScanSettings = new LastPnoScanSettings(
mClock.getElapsedSinceBootMillis(),
settings.networkList, eventHandler);
} else {
Log.e(TAG, "Failed to start PNO scan");
reportPnoScanFailure();
}
return true;
}
}
@Override
public boolean resetHwPnoList() {
synchronized (mSettingsLock) {
if (mLastPnoScanSettings == null) {
Log.w(TAG, "No PNO scan running");
return false;
}
mLastPnoScanSettings = null;
// For wificond based PNO, we stop the scan immediately when we reset pno list.
stopHwPnoScan();
return true;
}
}
@Override
public boolean isHwPnoSupported(boolean isConnectedPno) {
// Hw Pno Scan is supported only for disconnected PNO when the device supports it.
return isHwPnoScanRequired(isConnectedPno);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mSettingsLock) {
long nowMs = mClock.getElapsedSinceBootMillis();
pw.println("Latest native scan results:");
if (mNativeScanResults != null) {
List<ScanResult> scanResults = mNativeScanResults.stream().map(r -> {
return r.getScanResult();
}).collect(Collectors.toList());
ScanResultUtil.dumpScanResults(pw, scanResults, nowMs);
}
pw.println("Latest native pno scan results:");
if (mNativePnoScanResults != null) {
List<ScanResult> pnoScanResults = mNativePnoScanResults.stream().map(r -> {
return r.getScanResult();
}).collect(Collectors.toList());
ScanResultUtil.dumpScanResults(pw, pnoScanResults, nowMs);
}
}
}
private static class LastScanSettings {
LastScanSettings(long startTime,
boolean reportSingleScanFullResults,
ChannelCollection singleScanFreqs,
WifiNative.ScanEventHandler singleScanEventHandler) {
this.startTime = startTime;
this.reportSingleScanFullResults = reportSingleScanFullResults;
this.singleScanFreqs = singleScanFreqs;
this.singleScanEventHandler = singleScanEventHandler;
}
public long startTime;
public boolean reportSingleScanFullResults;
public ChannelCollection singleScanFreqs;
public WifiNative.ScanEventHandler singleScanEventHandler;
}
private static class LastPnoScanSettings {
LastPnoScanSettings(long startTime,
WifiNative.PnoNetwork[] pnoNetworkList,
WifiNative.PnoEventHandler pnoScanEventHandler) {
this.startTime = startTime;
this.pnoNetworkList = pnoNetworkList;
this.pnoScanEventHandler = pnoScanEventHandler;
}
public long startTime;
public WifiNative.PnoNetwork[] pnoNetworkList;
public WifiNative.PnoEventHandler pnoScanEventHandler;
}
}