/* * 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.WifiConfiguration; 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 java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Implementation of the WifiScanner HAL API that uses wpa_supplicant to perform all scans * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method. */ public class SupplicantWifiScannerImpl extends WifiScannerImpl implements Handler.Callback { private static final String TAG = "SupplicantWifiScannerImpl"; private static final boolean DBG = false; public static final String BACKGROUND_PERIOD_ALARM_TAG = TAG + " Background Scan Period"; public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout"; // Max number of networks that can be specified to wpa_supplicant 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 static final String ACTION_SCAN_PERIOD = "com.android.server.util.SupplicantWifiScannerImpl.action.SCAN_PERIOD"; private final Context mContext; private final WifiNative mWifiNative; private final AlarmManager mAlarmManager; private final Handler mEventHandler; private final ChannelHelper mChannelHelper; private final Clock mClock; private Object mSettingsLock = new Object(); // Next scan settings to apply when the previous scan completes private WifiNative.ScanSettings mPendingBackgroundScanSettings = null; private WifiNative.ScanEventHandler mPendingBackgroundScanEventHandler = null; private WifiNative.ScanSettings mPendingSingleScanSettings = null; private WifiNative.ScanEventHandler mPendingSingleScanEventHandler = null; // Active background scan settings/state private WifiNative.ScanSettings mBackgroundScanSettings = null; private WifiNative.ScanEventHandler mBackgroundScanEventHandler = null; private int mNextBackgroundScanPeriod = 0; private int mNextBackgroundScanId = 0; private boolean mBackgroundScanPeriodPending = false; private boolean mBackgroundScanPaused = false; private ScanBuffer mBackgroundScanBuffer = new ScanBuffer(SCAN_BUFFER_CAPACITY); private WifiScanner.ScanData mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, new ScanResult[0]); // Settings for the currently running scan, null if no scan active private LastScanSettings mLastScanSettings = null; // Active hotlist settings private WifiNative.HotlistEventHandler mHotlistHandler = null; private ChangeBuffer mHotlistChangeBuffer = new ChangeBuffer(); // Pno related info. private WifiNative.PnoSettings mPnoSettings = null; private WifiNative.PnoEventHandler mPnoEventHandler; private final boolean mHwPnoScanSupported; private final HwPnoDebouncer mHwPnoDebouncer; private final HwPnoDebouncer.Listener mHwPnoDebouncerListener = new HwPnoDebouncer.Listener() { public void onPnoScanFailed() { Log.e(TAG, "Pno scan failure received"); reportPnoScanFailure(); } }; /** * 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; AlarmManager.OnAlarmListener mScanPeriodListener = new AlarmManager.OnAlarmListener() { public void onAlarm() { synchronized (mSettingsLock) { handleScanPeriod(); } } }; AlarmManager.OnAlarmListener mScanTimeoutListener = new AlarmManager.OnAlarmListener() { public void onAlarm() { synchronized (mSettingsLock) { handleScanTimeout(); } } }; public SupplicantWifiScannerImpl(Context context, WifiNative wifiNative, ChannelHelper channelHelper, Looper looper, Clock clock) { mContext = context; mWifiNative = wifiNative; mChannelHelper = channelHelper; mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mEventHandler = new Handler(looper, this); mClock = clock; mHwPnoDebouncer = new HwPnoDebouncer(mWifiNative, mAlarmManager, mEventHandler, mClock); // Check if the device supports HW PNO scans. mHwPnoScanSupported = mContext.getResources().getBoolean( R.bool.config_wifi_background_scan_support); WifiMonitor.getInstance().registerHandler(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_FAILED_EVENT, mEventHandler); WifiMonitor.getInstance().registerHandler(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler); } public SupplicantWifiScannerImpl(Context context, WifiNative wifiNative, Looper looper, Clock clock) { // TODO figure out how to get channel information from supplicant this(context, wifiNative, new NoBandChannelHelper(), looper, clock); } @Override public void cleanup() { synchronized (mSettingsLock) { mPendingSingleScanSettings = null; mPendingSingleScanEventHandler = null; stopHwPnoScan(); stopBatchedScan(); resetHotlist(); untrackSignificantWifiChange(); mLastScanSettings = null; // finally clear any active scan } } @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; capabilities.max_hotlist_bssids = 0; capabilities.max_significant_wifi_change_aps = 0; 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; } if (mPendingSingleScanSettings != null || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) { Log.w(TAG, "A single scan is already running"); return false; } synchronized (mSettingsLock) { mPendingSingleScanSettings = settings; mPendingSingleScanEventHandler = eventHandler; processPendingScans(); return true; } } @Override public WifiScanner.ScanData getLatestSingleScanResults() { return mLatestSingleScanResult; } @Override public boolean startBatchedScan(WifiNative.ScanSettings settings, WifiNative.ScanEventHandler eventHandler) { if (settings == null || eventHandler == null) { Log.w(TAG, "Invalid arguments for startBatched: settings=" + settings + ",eventHandler=" + eventHandler); return false; } if (settings.max_ap_per_scan < 0 || settings.max_ap_per_scan > MAX_APS_PER_SCAN) { return false; } if (settings.num_buckets < 0 || settings.num_buckets > MAX_SCAN_BUCKETS) { return false; } if (settings.report_threshold_num_scans < 0 || settings.report_threshold_num_scans > SCAN_BUFFER_CAPACITY) { return false; } if (settings.report_threshold_percent < 0 || settings.report_threshold_percent > 100) { return false; } if (settings.base_period_ms <= 0) { return false; } for (int i = 0; i < settings.num_buckets; ++i) { WifiNative.BucketSettings bucket = settings.buckets[i]; if (bucket.period_ms % settings.base_period_ms != 0) { return false; } } synchronized (mSettingsLock) { stopBatchedScan(); if (DBG) { Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period=" + settings.base_period_ms + " ms"); } mPendingBackgroundScanSettings = settings; mPendingBackgroundScanEventHandler = eventHandler; handleScanPeriod(); // Try to start scan immediately return true; } } @Override public void stopBatchedScan() { synchronized (mSettingsLock) { if (DBG) Log.d(TAG, "Stopping scan"); mBackgroundScanSettings = null; mBackgroundScanEventHandler = null; mPendingBackgroundScanSettings = null; mPendingBackgroundScanEventHandler = null; mBackgroundScanPaused = false; mBackgroundScanPeriodPending = false; unscheduleScansLocked(); } processPendingScans(); } @Override public void pauseBatchedScan() { synchronized (mSettingsLock) { if (DBG) Log.d(TAG, "Pausing scan"); // if there isn't a pending scan then make the current scan pending if (mPendingBackgroundScanSettings == null) { mPendingBackgroundScanSettings = mBackgroundScanSettings; mPendingBackgroundScanEventHandler = mBackgroundScanEventHandler; } mBackgroundScanSettings = null; mBackgroundScanEventHandler = null; mBackgroundScanPeriodPending = false; mBackgroundScanPaused = true; unscheduleScansLocked(); WifiScanner.ScanData[] results = getLatestBatchedScanResults(/* flush = */ true); if (mPendingBackgroundScanEventHandler != null) { mPendingBackgroundScanEventHandler.onScanPaused(results); } } processPendingScans(); } @Override public void restartBatchedScan() { synchronized (mSettingsLock) { if (DBG) Log.d(TAG, "Restarting scan"); if (mPendingBackgroundScanEventHandler != null) { mPendingBackgroundScanEventHandler.onScanRestarted(); } mBackgroundScanPaused = false; handleScanPeriod(); } } private void unscheduleScansLocked() { mAlarmManager.cancel(mScanPeriodListener); if (mLastScanSettings != null) { mLastScanSettings.backgroundScanActive = false; } } private void handleScanPeriod() { synchronized (mSettingsLock) { mBackgroundScanPeriodPending = true; processPendingScans(); } } private void handleScanTimeout() { Log.e(TAG, "Timed out waiting for scan result from supplicant"); reportScanFailure(); processPendingScans(); } private void processPendingScans() { synchronized (mSettingsLock) { // Wait for the active scan result to come back to reschedule other scans, // unless if HW pno scan is running. Hw PNO scans are paused it if there // are other pending scans, if (mLastScanSettings != null && !mLastScanSettings.hwPnoScanActive) { return; } ChannelCollection allFreqs = mChannelHelper.createChannelCollection(); Set hiddenNetworkIdSet = new HashSet(); final LastScanSettings newScanSettings = new LastScanSettings(mClock.elapsedRealtime()); // Update scan settings if there is a pending scan if (!mBackgroundScanPaused) { if (mPendingBackgroundScanSettings != null) { mBackgroundScanSettings = mPendingBackgroundScanSettings; mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler; mNextBackgroundScanPeriod = 0; mPendingBackgroundScanSettings = null; mPendingBackgroundScanEventHandler = null; mBackgroundScanPeriodPending = true; } if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) { int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets; ++bucket_id) { WifiNative.BucketSettings bucket = mBackgroundScanSettings.buckets[bucket_id]; if (mNextBackgroundScanPeriod % (bucket.period_ms / mBackgroundScanSettings.base_period_ms) == 0) { if ((bucket.report_events & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) { reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; } if ((bucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; } // only no batch if all buckets specify it if ((bucket.report_events & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) { reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH; } allFreqs.addChannels(bucket); } } if (!allFreqs.isEmpty()) { newScanSettings.setBackgroundScan(mNextBackgroundScanId++, mBackgroundScanSettings.max_ap_per_scan, reportEvents, mBackgroundScanSettings.report_threshold_num_scans, mBackgroundScanSettings.report_threshold_percent); } int[] hiddenNetworkIds = mBackgroundScanSettings.hiddenNetworkIds; if (hiddenNetworkIds != null) { int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN); for (int i = 0; i < numHiddenNetworkIds; i++) { hiddenNetworkIdSet.add(hiddenNetworkIds[i]); } } mNextBackgroundScanPeriod++; mBackgroundScanPeriodPending = false; mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + mBackgroundScanSettings.base_period_ms, BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler); } } if (mPendingSingleScanSettings != null) { boolean reportFullResults = false; ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection(); for (int i = 0; i < mPendingSingleScanSettings.num_buckets; ++i) { WifiNative.BucketSettings bucketSettings = mPendingSingleScanSettings.buckets[i]; if ((bucketSettings.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { reportFullResults = true; } singleScanFreqs.addChannels(bucketSettings); allFreqs.addChannels(bucketSettings); } newScanSettings.setSingleScan(reportFullResults, singleScanFreqs, mPendingSingleScanEventHandler); int[] hiddenNetworkIds = mPendingSingleScanSettings.hiddenNetworkIds; if (hiddenNetworkIds != null) { int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN); for (int i = 0; i < numHiddenNetworkIds; i++) { hiddenNetworkIdSet.add(hiddenNetworkIds[i]); } } mPendingSingleScanSettings = null; mPendingSingleScanEventHandler = null; } if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive) && !allFreqs.isEmpty()) { pauseHwPnoScan(); Set freqs = allFreqs.getSupplicantScanFreqs(); boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet); if (success) { // TODO handle scan timeout if (DBG) { Log.d(TAG, "Starting wifi scan for freqs=" + freqs + ", background=" + newScanSettings.backgroundScanActive + ", single=" + newScanSettings.singleScanActive); } mLastScanSettings = newScanSettings; mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + SCAN_TIMEOUT_MS, TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler); } else { Log.e(TAG, "Failed to start scan, freqs=" + freqs); // indicate scan failure async mEventHandler.post(new Runnable() { public void run() { if (newScanSettings.singleScanEventHandler != null) { newScanSettings.singleScanEventHandler .onScanStatus(WifiNative.WIFI_SCAN_FAILED); } } }); // TODO(b/27769665) background scans should be failed too if scans fail enough } } else if (isHwPnoScanRequired()) { newScanSettings.setHwPnoScan(mPnoEventHandler); if (startHwPnoScan()) { mLastScanSettings = newScanSettings; } else { Log.e(TAG, "Failed to start PNO scan"); // indicate scan failure async mEventHandler.post(new Runnable() { public void run() { if (mPnoEventHandler != null) { mPnoEventHandler.onPnoScanFailed(); } // Clean up PNO state, we don't want to continue PNO scanning. mPnoSettings = null; mPnoEventHandler = null; } }); } } } } @Override public boolean handleMessage(Message msg) { switch(msg.what) { case WifiMonitor.SCAN_FAILED_EVENT: Log.w(TAG, "Scan failed"); mAlarmManager.cancel(mScanTimeoutListener); reportScanFailure(); processPendingScans(); break; case WifiMonitor.SCAN_RESULTS_EVENT: mAlarmManager.cancel(mScanTimeoutListener); pollLatestScanData(); processPendingScans(); break; default: // ignore unknown event } return true; } private void reportScanFailure() { synchronized (mSettingsLock) { if (mLastScanSettings != null) { if (mLastScanSettings.singleScanEventHandler != null) { mLastScanSettings.singleScanEventHandler .onScanStatus(WifiNative.WIFI_SCAN_FAILED); } // TODO(b/27769665) background scans should be failed too if scans fail enough mLastScanSettings = null; } } } private void reportPnoScanFailure() { synchronized (mSettingsLock) { if (mLastScanSettings != null && mLastScanSettings.hwPnoScanActive) { if (mLastScanSettings.pnoScanEventHandler != null) { mLastScanSettings.pnoScanEventHandler.onPnoScanFailed(); } // Clean up PNO state, we don't want to continue PNO scanning. mPnoSettings = null; mPnoEventHandler = null; mLastScanSettings = null; } } } private void pollLatestScanData() { synchronized (mSettingsLock) { if (mLastScanSettings == null) { // got a scan before we started scanning or after scan was canceled return; } if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId); ArrayList nativeResults = mWifiNative.getScanResults(); List singleScanResults = new ArrayList<>(); List backgroundScanResults = new ArrayList<>(); List hwPnoScanResults = new ArrayList<>(); for (int i = 0; i < nativeResults.size(); ++i) { ScanResult result = nativeResults.get(i).getScanResult(); long timestamp_ms = result.timestamp / 1000; // convert us -> ms if (timestamp_ms > mLastScanSettings.startTime) { if (mLastScanSettings.backgroundScanActive) { backgroundScanResults.add(result); } if (mLastScanSettings.singleScanActive && mLastScanSettings.singleScanFreqs.containsChannel( result.frequency)) { singleScanResults.add(result); } if (mLastScanSettings.hwPnoScanActive) { hwPnoScanResults.add(result); } } else { // was a cached result in wpa_supplicant } } if (mLastScanSettings.backgroundScanActive) { if (mBackgroundScanEventHandler != null) { if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { for (ScanResult scanResult : backgroundScanResults) { // TODO(b/27506257): Fill in correct bucketsScanned value mBackgroundScanEventHandler.onFullScanResult(scanResult, 0); } } } Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR); ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps, backgroundScanResults.size())]; for (int i = 0; i < scanResultsArray.length; ++i) { scanResultsArray[i] = backgroundScanResults.get(i); } if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) { // TODO(b/27506257): Fill in correct bucketsScanned value mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0, scanResultsArray)); } if (mBackgroundScanEventHandler != null) { if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0 || (mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0 || (mLastScanSettings.reportEvents == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL && (mBackgroundScanBuffer.size() >= (mBackgroundScanBuffer.capacity() * mLastScanSettings.reportPercentThreshold / 100) || mBackgroundScanBuffer.size() >= mLastScanSettings.reportNumScansThreshold))) { mBackgroundScanEventHandler .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE); } } if (mHotlistHandler != null) { int event = mHotlistChangeBuffer.processScan(backgroundScanResults); if ((event & ChangeBuffer.EVENT_FOUND) != 0) { mHotlistHandler.onHotlistApFound( mHotlistChangeBuffer.getLastResults(ChangeBuffer.EVENT_FOUND)); } if ((event & ChangeBuffer.EVENT_LOST) != 0) { mHotlistHandler.onHotlistApLost( mHotlistChangeBuffer.getLastResults(ChangeBuffer.EVENT_LOST)); } } } if (mLastScanSettings.singleScanActive && 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(mLastScanSettings.scanId, 0, singleScanResults.toArray(new ScanResult[singleScanResults.size()])); mLastScanSettings.singleScanEventHandler .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE); } if (mLastScanSettings.hwPnoScanActive && mLastScanSettings.pnoScanEventHandler != null) { ScanResult[] pnoScanResultsArray = new ScanResult[hwPnoScanResults.size()]; for (int i = 0; i < pnoScanResultsArray.length; ++i) { pnoScanResultsArray[i] = hwPnoScanResults.get(i); } mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray); } mLastScanSettings = null; } } @Override public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) { synchronized (mSettingsLock) { WifiScanner.ScanData[] results = mBackgroundScanBuffer.get(); if (flush) { mBackgroundScanBuffer.clear(); } return results; } } private boolean setNetworkPriorities(WifiNative.PnoNetwork[] networkList) { if (networkList != null) { if (DBG) Log.i(TAG, "Enable network and Set priorities for PNO."); for (WifiNative.PnoNetwork network : networkList) { if (!mWifiNative.setNetworkVariable(network.networkId, WifiConfiguration.priorityVarName, Integer.toString(network.priority))) { Log.e(TAG, "Set priority failed for: " + network.networkId); return false; } if (!mWifiNative.enableNetworkWithoutConnect(network.networkId)) { Log.e(TAG, "Enable network failed for: " + network.networkId); return false; } } } return true; } private boolean startHwPnoScan() { return mHwPnoDebouncer.startPnoScan(mHwPnoDebouncerListener); } private void stopHwPnoScan() { mHwPnoDebouncer.stopPnoScan(); } private void pauseHwPnoScan() { mHwPnoDebouncer.forceStopPnoScan(); } /** * 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); } private boolean isHwPnoScanRequired() { if (mPnoSettings == null) return false; return isHwPnoScanRequired(mPnoSettings.isConnected); } @Override public boolean setHwPnoList(WifiNative.PnoSettings settings, WifiNative.PnoEventHandler eventHandler) { synchronized (mSettingsLock) { if (mPnoSettings != null) { Log.w(TAG, "Already running a PNO scan"); return false; } mPnoEventHandler = eventHandler; mPnoSettings = settings; if (!setNetworkPriorities(settings.networkList)) return false; // For supplicant based PNO, we start the scan immediately when we set pno list. processPendingScans(); return true; } } @Override public boolean resetHwPnoList() { synchronized (mSettingsLock) { if (mPnoSettings == null) { Log.w(TAG, "No PNO scan running"); return false; } mPnoEventHandler = null; mPnoSettings = null; // For supplicant 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 public boolean shouldScheduleBackgroundScanForHwPno() { return false; } @Override public boolean setHotlist(WifiScanner.HotlistSettings settings, WifiNative.HotlistEventHandler eventHandler) { if (settings == null || eventHandler == null) { return false; } synchronized (mSettingsLock) { mHotlistHandler = eventHandler; mHotlistChangeBuffer.setSettings(settings.bssidInfos, settings.apLostThreshold, 1); return true; } } @Override public void resetHotlist() { synchronized (mSettingsLock) { mHotlistChangeBuffer.clearSettings(); mHotlistHandler = null; } } /* * Significant Wifi Change API is not implemented */ @Override public boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings, WifiNative.SignificantWifiChangeEventHandler handler) { return false; } @Override public void untrackSignificantWifiChange() {} private static class LastScanSettings { public long startTime; public LastScanSettings(long startTime) { this.startTime = startTime; } // Background settings public boolean backgroundScanActive = false; public int scanId; public int maxAps; public int reportEvents; public int reportNumScansThreshold; public int reportPercentThreshold; public void setBackgroundScan(int scanId, int maxAps, int reportEvents, int reportNumScansThreshold, int reportPercentThreshold) { this.backgroundScanActive = true; this.scanId = scanId; this.maxAps = maxAps; this.reportEvents = reportEvents; this.reportNumScansThreshold = reportNumScansThreshold; this.reportPercentThreshold = reportPercentThreshold; } // Single scan settings public boolean singleScanActive = false; public boolean reportSingleScanFullResults; public ChannelCollection singleScanFreqs; public WifiNative.ScanEventHandler singleScanEventHandler; public void setSingleScan(boolean reportSingleScanFullResults, ChannelCollection singleScanFreqs, WifiNative.ScanEventHandler singleScanEventHandler) { singleScanActive = true; this.reportSingleScanFullResults = reportSingleScanFullResults; this.singleScanFreqs = singleScanFreqs; this.singleScanEventHandler = singleScanEventHandler; } public boolean hwPnoScanActive = false; public WifiNative.PnoEventHandler pnoScanEventHandler; public void setHwPnoScan(WifiNative.PnoEventHandler pnoScanEventHandler) { hwPnoScanActive = true; this.pnoScanEventHandler = pnoScanEventHandler; } } private static class ScanBuffer { private final ArrayDeque mBuffer; private int mCapacity; public ScanBuffer(int capacity) { mCapacity = capacity; mBuffer = new ArrayDeque<>(mCapacity); } public int size() { return mBuffer.size(); } public int capacity() { return mCapacity; } public boolean isFull() { return size() == mCapacity; } public void add(WifiScanner.ScanData scanData) { if (isFull()) { mBuffer.pollFirst(); } mBuffer.offerLast(scanData); } public void clear() { mBuffer.clear(); } public WifiScanner.ScanData[] get() { return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]); } } private static class ChangeBuffer { public static int EVENT_NONE = 0; public static int EVENT_LOST = 1; public static int EVENT_FOUND = 2; public static int STATE_FOUND = 0; private WifiScanner.BssidInfo[] mBssidInfos = null; private int mApLostThreshold; private int mMinEvents; private int[] mLostCount = null; private ScanResult[] mMostRecentResult = null; private int[] mPendingEvent = null; private boolean mFiredEvents = false; private static ScanResult findResult(List results, String bssid) { for (int i = 0; i < results.size(); ++i) { if (bssid.equalsIgnoreCase(results.get(i).BSSID)) { return results.get(i); } } return null; } public void setSettings(WifiScanner.BssidInfo[] bssidInfos, int apLostThreshold, int minEvents) { mBssidInfos = bssidInfos; if (apLostThreshold <= 0) { mApLostThreshold = 1; } else { mApLostThreshold = apLostThreshold; } mMinEvents = minEvents; if (bssidInfos != null) { mLostCount = new int[bssidInfos.length]; Arrays.fill(mLostCount, mApLostThreshold); // default to lost mMostRecentResult = new ScanResult[bssidInfos.length]; mPendingEvent = new int[bssidInfos.length]; mFiredEvents = false; } else { mLostCount = null; mMostRecentResult = null; mPendingEvent = null; } } public void clearSettings() { setSettings(null, 0, 0); } /** * Get the most recent scan results for APs that triggered the given event on the last call * to {@link #processScan}. */ public ScanResult[] getLastResults(int event) { ArrayList results = new ArrayList<>(); for (int i = 0; i < mLostCount.length; ++i) { if (mPendingEvent[i] == event) { results.add(mMostRecentResult[i]); } } return results.toArray(new ScanResult[results.size()]); } /** * Process the supplied scan results and determine if any events should be generated based * on the configured settings * @return The events that occurred */ public int processScan(List scanResults) { if (mBssidInfos == null) { return EVENT_NONE; } // clear events from last time if (mFiredEvents) { mFiredEvents = false; for (int i = 0; i < mLostCount.length; ++i) { mPendingEvent[i] = EVENT_NONE; } } int eventCount = 0; int eventType = EVENT_NONE; for (int i = 0; i < mLostCount.length; ++i) { ScanResult result = findResult(scanResults, mBssidInfos[i].bssid); int rssi = Integer.MIN_VALUE; if (result != null) { mMostRecentResult[i] = result; rssi = result.level; } if (rssi < mBssidInfos[i].low) { if (mLostCount[i] < mApLostThreshold) { mLostCount[i]++; if (mLostCount[i] >= mApLostThreshold) { if (mPendingEvent[i] == EVENT_FOUND) { mPendingEvent[i] = EVENT_NONE; } else { mPendingEvent[i] = EVENT_LOST; } } } } else { if (mLostCount[i] >= mApLostThreshold) { if (mPendingEvent[i] == EVENT_LOST) { mPendingEvent[i] = EVENT_NONE; } else { mPendingEvent[i] = EVENT_FOUND; } } mLostCount[i] = STATE_FOUND; } if (DBG) { Log.d(TAG, "ChangeBuffer BSSID: " + mBssidInfos[i].bssid + "=" + mLostCount[i] + ", " + mPendingEvent[i] + ", rssi=" + rssi); } if (mPendingEvent[i] != EVENT_NONE) { ++eventCount; eventType |= mPendingEvent[i]; } } if (DBG) Log.d(TAG, "ChangeBuffer events count=" + eventCount + ": " + eventType); if (eventCount >= mMinEvents) { mFiredEvents = true; return eventType; } return EVENT_NONE; } } /** * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO * state too often which is not handled very well by some drivers. * Note: This is not thread safe! */ public static class HwPnoDebouncer { public static final String PNO_DEBOUNCER_ALARM_TAG = TAG + "Pno Monitor"; private static final int MINIMUM_PNO_GAP_MS = 5 * 1000; private final WifiNative mWifiNative; private final AlarmManager mAlarmManager; private final Handler mEventHandler; private final Clock mClock; private long mLastPnoChangeTimeStamp = -1L; private boolean mExpectedPnoState = false; private boolean mCurrentPnoState = false;; private boolean mWaitForTimer = false; private Listener mListener; /** * Interface used to indicate PNO scan notifications. */ public interface Listener { /** * Used to indicate a delayed PNO scan request failure. */ void onPnoScanFailed(); } public HwPnoDebouncer(WifiNative wifiNative, AlarmManager alarmManager, Handler eventHandler, Clock clock) { mWifiNative = wifiNative; mAlarmManager = alarmManager; mEventHandler = eventHandler; mClock = clock; } /** * Enable/Disable PNO state in wpa_supplicant * @param enable boolean indicating whether PNO is being enabled or disabled. */ private boolean updatePnoState(boolean enable) { if (mCurrentPnoState == enable) { if (DBG) Log.d(TAG, "PNO state is already " + enable); return true; } mLastPnoChangeTimeStamp = mClock.elapsedRealtime(); if (mWifiNative.setPnoScan(enable)) { Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to " + enable); mCurrentPnoState = enable; return true; } else { Log.e(TAG, "PNO state change to " + enable + " failed"); return false; } } private final AlarmManager.OnAlarmListener mAlarmListener = new AlarmManager.OnAlarmListener() { public void onAlarm() { if (DBG) Log.d(TAG, "PNO timer expired, expected state " + mExpectedPnoState); if (!updatePnoState(mExpectedPnoState)) { if (mListener != null) { mListener.onPnoScanFailed(); } } mWaitForTimer = false; } }; /** * Enable/Disable PNO state. This method will debounce PNO scan requests. * @param enable boolean indicating whether PNO is being enabled or disabled. */ private boolean setPnoState(boolean enable) { boolean isSuccess = true; mExpectedPnoState = enable; if (!mWaitForTimer) { long timeDifference = mClock.elapsedRealtime() - mLastPnoChangeTimeStamp; if (timeDifference >= MINIMUM_PNO_GAP_MS) { isSuccess = updatePnoState(enable); } else { long alarmTimeout = MINIMUM_PNO_GAP_MS - timeDifference; Log.d(TAG, "Start PNO timer with delay " + alarmTimeout); mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + alarmTimeout, PNO_DEBOUNCER_ALARM_TAG, mAlarmListener, mEventHandler); mWaitForTimer = true; } } return isSuccess; } /** * Start PNO scan */ public boolean startPnoScan(Listener listener) { if (DBG) Log.d(TAG, "Starting PNO scan"); mListener = listener; if (!setPnoState(true)) { mListener = null; return false; } return true; } /** * Stop PNO scan */ public void stopPnoScan() { if (DBG) Log.d(TAG, "Stopping PNO scan"); setPnoState(false); mListener = null; } /** * Force stop PNO scanning. This method will bypass the debounce logic and stop PNO * scan immediately. */ public void forceStopPnoScan() { if (mCurrentPnoState) { if (DBG) Log.d(TAG, "Force stopping Pno scan"); // Cancel the debounce timer and stop PNO scan. if (mWaitForTimer) { mAlarmManager.cancel(mAlarmListener); mWaitForTimer = false; } updatePnoState(false); } } } }