| /* |
| * 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; |
| |
| } |
| |
| } |