From a8367288377cbaed6371256ca837b7aa22280706 Mon Sep 17 00:00:00 2001 From: Mitchell Wills Date: Mon, 11 Apr 2016 13:40:57 -0700 Subject: Move WifiScanner related code to scanner package Change-Id: I73d01fe4d0de99a4e9758353cacf1d7a790cf040 --- .../android/server/wifi/HalWifiScannerImpl.java | 179 -- .../server/wifi/SupplicantWifiScannerImpl.java | 1117 --------- .../com/android/server/wifi/WifiScannerImpl.java | 178 -- .../android/server/wifi/WifiScanningScheduler.java | 0 .../android/server/wifi/WifiScanningService.java | 55 - .../server/wifi/WifiScanningServiceImpl.java | 2599 ------------------- .../server/wifi/scanner/HalWifiScannerImpl.java | 177 ++ .../wifi/scanner/SupplicantWifiScannerImpl.java | 1118 +++++++++ .../server/wifi/scanner/WifiScannerImpl.java | 178 ++ .../server/wifi/scanner/WifiScanningService.java | 56 + .../wifi/scanner/WifiScanningServiceImpl.java | 2601 ++++++++++++++++++++ 11 files changed, 4130 insertions(+), 4128 deletions(-) delete mode 100644 service/java/com/android/server/wifi/HalWifiScannerImpl.java delete mode 100644 service/java/com/android/server/wifi/SupplicantWifiScannerImpl.java delete mode 100644 service/java/com/android/server/wifi/WifiScannerImpl.java delete mode 100644 service/java/com/android/server/wifi/WifiScanningScheduler.java delete mode 100644 service/java/com/android/server/wifi/WifiScanningService.java delete mode 100644 service/java/com/android/server/wifi/WifiScanningServiceImpl.java create mode 100644 service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java create mode 100644 service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java create mode 100644 service/java/com/android/server/wifi/scanner/WifiScannerImpl.java create mode 100644 service/java/com/android/server/wifi/scanner/WifiScanningService.java create mode 100644 service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java (limited to 'service') diff --git a/service/java/com/android/server/wifi/HalWifiScannerImpl.java b/service/java/com/android/server/wifi/HalWifiScannerImpl.java deleted file mode 100644 index 24942ad3c..000000000 --- a/service/java/com/android/server/wifi/HalWifiScannerImpl.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiScanner; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Log; - -import com.android.server.wifi.scanner.ChannelHelper; -import com.android.server.wifi.scanner.HalChannelHelper; - -/** - * WifiScanner implementation that takes advantage of the gscan HAL API - * The gscan API is used to perform background scans and wpa_supplicant is used for onehot scans. - * @see com.android.server.wifi.WifiScannerImpl for more details on each method - */ -public class HalWifiScannerImpl extends WifiScannerImpl implements Handler.Callback { - private static final String TAG = "HalWifiScannerImpl"; - private static final boolean DBG = false; - - private final WifiNative mWifiNative; - private final ChannelHelper mChannelHelper; - private final SupplicantWifiScannerImpl mSupplicantScannerDelegate; - private final boolean mHalBasedPnoSupported; - - public HalWifiScannerImpl(Context context, WifiNative wifiNative, Looper looper) { - mWifiNative = wifiNative; - mChannelHelper = new HalChannelHelper(wifiNative); - mSupplicantScannerDelegate = - new SupplicantWifiScannerImpl(context, wifiNative, mChannelHelper, looper); - - // Check if ePNO is supported by the HAL. - int halFeatureSet = mWifiNative.getSupportedFeatureSet(); - mHalBasedPnoSupported = false; - /* TODO(b/27877781): Swith ePNO on - mHalBasedPnoSupported = - ((halFeatureSet & WifiManager.WIFI_FEATURE_HAL_EPNO) - == WifiManager.WIFI_FEATURE_HAL_EPNO); */ - } - - @Override - public boolean handleMessage(Message msg) { - Log.w(TAG, "Unknown message received: " + msg.what); - return true; - } - - @Override - public void cleanup() { - mSupplicantScannerDelegate.cleanup(); - } - - @Override - public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) { - return mWifiNative.getScanCapabilities(capabilities); - } - - @Override - public ChannelHelper getChannelHelper() { - return mChannelHelper; - } - - public boolean startSingleScan(WifiNative.ScanSettings settings, - WifiNative.ScanEventHandler eventHandler) { - return mSupplicantScannerDelegate.startSingleScan(settings, eventHandler); - } - - @Override - public WifiScanner.ScanData getLatestSingleScanResults() { - return mSupplicantScannerDelegate.getLatestSingleScanResults(); - } - - @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; - } - return mWifiNative.startScan(settings, eventHandler); - } - - @Override - public void stopBatchedScan() { - mWifiNative.stopScan(); - } - - @Override - public void pauseBatchedScan() { - mWifiNative.pauseScan(); - } - - @Override - public void restartBatchedScan() { - mWifiNative.restartScan(); - } - - @Override - public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) { - return mWifiNative.getScanResults(flush); - } - - @Override - public boolean setHwPnoList(WifiNative.PnoSettings settings, - WifiNative.PnoEventHandler eventHandler) { - if (mHalBasedPnoSupported) { - return mWifiNative.setPnoList(settings, eventHandler); - } else { - return mSupplicantScannerDelegate.setHwPnoList(settings, eventHandler); - } - } - - @Override - public boolean resetHwPnoList() { - if (mHalBasedPnoSupported) { - return mWifiNative.resetPnoList(); - } else { - return mSupplicantScannerDelegate.resetHwPnoList(); - } - } - - @Override - public boolean isHwPnoSupported(boolean isConnectedPno) { - if (mHalBasedPnoSupported) { - return true; - } else { - return mSupplicantScannerDelegate.isHwPnoSupported(isConnectedPno); - } - } - - @Override - public boolean shouldScheduleBackgroundScanForHwPno() { - if (mHalBasedPnoSupported) { - return true; - } else { - return mSupplicantScannerDelegate.shouldScheduleBackgroundScanForHwPno(); - } - } - - @Override - public boolean setHotlist(WifiScanner.HotlistSettings settings, - WifiNative.HotlistEventHandler eventHandler) { - return mWifiNative.setHotlist(settings, eventHandler); - } - - @Override - public void resetHotlist() { - mWifiNative.resetHotlist(); - } - - @Override - public boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings, - WifiNative.SignificantWifiChangeEventHandler handler) { - return mWifiNative.trackSignificantWifiChange(settings, handler); - } - - @Override - public void untrackSignificantWifiChange() { - mWifiNative.untrackSignificantWifiChange(); - } -} diff --git a/service/java/com/android/server/wifi/SupplicantWifiScannerImpl.java b/service/java/com/android/server/wifi/SupplicantWifiScannerImpl.java deleted file mode 100644 index 1eb7d4b2b..000000000 --- a/service/java/com/android/server/wifi/SupplicantWifiScannerImpl.java +++ /dev/null @@ -1,1117 +0,0 @@ -/* - * 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; - -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.os.SystemClock; -import android.util.Log; - -import com.android.internal.R; -import com.android.server.wifi.scanner.ChannelHelper; -import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; -import com.android.server.wifi.scanner.NoBandChannelHelper; - -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.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"; - - 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 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 = 10000; - - 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) { - mContext = context; - mWifiNative = wifiNative; - mChannelHelper = channelHelper; - mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - mEventHandler = new Handler(looper, this); - mHwPnoDebouncer = new HwPnoDebouncer(mWifiNative, mAlarmManager, mEventHandler); - - // 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) { - // TODO figure out how to get channel information from supplicant - this(context, wifiNative, new NoBandChannelHelper(), looper); - } - - @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(); - 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(SystemClock.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) { - if (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) { - for (int i = 0; i < hiddenNetworkIds.length; i++) { - hiddenNetworkIdSet.add(hiddenNetworkIds[i]); - } - } - } - - mNextBackgroundScanPeriod++; - mBackgroundScanPeriodPending = false; - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + 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) { - for (int i = 0; i < hiddenNetworkIds.length; 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 - Log.d(TAG, "Starting wifi scan for freqs=" + freqs - + ", background=" + newScanSettings.backgroundScanActive - + ", single=" + newScanSettings.singleScanActive); - mLastScanSettings = newScanSettings; - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + 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()) { - Log.d(TAG, "Starting wifi PNO scan"); - 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 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) { - mWifiNative = wifiNative; - mAlarmManager = alarmManager; - mEventHandler = eventHandler; - } - - /** - * 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; - } - Log.d(TAG, "Change PNO state from " + mCurrentPnoState + " to " + enable); - - mLastPnoChangeTimeStamp = System.currentTimeMillis(); - if (mWifiNative.setPnoScan(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 = System.currentTimeMillis() - 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.RTC_WAKEUP, - System.currentTimeMillis() + 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); - } - } - } -} diff --git a/service/java/com/android/server/wifi/WifiScannerImpl.java b/service/java/com/android/server/wifi/WifiScannerImpl.java deleted file mode 100644 index 5ade2a73e..000000000 --- a/service/java/com/android/server/wifi/WifiScannerImpl.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiScanner; -import android.os.Looper; - -import com.android.server.wifi.scanner.ChannelHelper; - -import java.util.Comparator; - -/** - * Defines the interface to the Wifi hardware required for the WifiScanner API - */ -public abstract class WifiScannerImpl { - - /** - * A factory that create a {@link com.android.server.wifi.WifiScannerImpl} - */ - public static interface WifiScannerImplFactory { - WifiScannerImpl create(Context context, Looper looper); - } - - /** - * Factory that create the implementation that is most appropriate for the system. - * This factory should only ever be used once. - */ - public static final WifiScannerImplFactory DEFAULT_FACTORY = new WifiScannerImplFactory() { - public WifiScannerImpl create(Context context, Looper looper) { - WifiNative wifiNative = WifiNative.getWlanNativeInterface(); - if (wifiNative.getScanCapabilities(new WifiNative.ScanCapabilities())) { - return new HalWifiScannerImpl(context, wifiNative, looper); - } else { - return new SupplicantWifiScannerImpl(context, wifiNative, looper); - } - } - }; - - /** - * A comparator that implements the sort order that is expected for scan results - */ - protected static final Comparator SCAN_RESULT_SORT_COMPARATOR = - new Comparator() { - public int compare(ScanResult r1, ScanResult r2) { - return r2.level - r1.level; - } - }; - - /** - * Cleanup any ongoing operations. This may be called when the driver is unloaded. - * There is no expectation that failure events are returned for ongoing operations. - */ - public abstract void cleanup(); - - /** - * Get the supported scan capabilities. - * - * @param capabilities Object that will be filled with the supported capabilities if successful - * @return true if the scan capabilities were retrieved successfully - */ - public abstract boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities); - - /** - * Get a ChannelHelper that can be used to perform operations on scan channels - */ - public abstract ChannelHelper getChannelHelper(); - - /** - * Start a one time scan. This method should only be called when there is no scan going on - * (after a callback indicating that the previous scan succeeded/failed). - * @return if the scan paramaters are valid - * Note this may return true even if the parameters are not accepted by the chip because the - * scan may be scheduled async. - */ - public abstract boolean startSingleScan(WifiNative.ScanSettings settings, - WifiNative.ScanEventHandler eventHandler); - /** - * Get the scan results of the most recent single scan. This should be called immediately when - * the scan success callback is receieved. - */ - public abstract WifiScanner.ScanData getLatestSingleScanResults(); - - /** - * Start a background scan. Calling this method while a background scan is already in process - * will interrupt the previous scan settings and replace it with the new ones. - * @return if the scan paramaters are valid - * Note this may return true even if the parameters are not accepted by the chip because the - * scan may be scheduled async. - */ - public abstract boolean startBatchedScan(WifiNative.ScanSettings settings, - WifiNative.ScanEventHandler eventHandler); - /** - * Stop the currently active background scan - */ - public abstract void stopBatchedScan(); - - /** - * Pause the currently active background scan - */ - public abstract void pauseBatchedScan(); - - /** - * Restart the currently paused background scan - */ - public abstract void restartBatchedScan(); - - /** - * Get the latest cached scan results from the last scan event. This should be called - * immediately when the scan success callback is receieved. - */ - public abstract WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush); - - /** - * Set PNO list to start PNO background scan. - * @param settings PNO settings for this scan. - * @param eventHandler Event handler for notifying the scan results. - * @return true if success, false otherwise - */ - public abstract boolean setHwPnoList(WifiNative.PnoSettings settings, - WifiNative.PnoEventHandler eventHandler); - - /** - * Reset PNO list to terminate PNO background scan. - * @return true if success, false otherwise - */ - public abstract boolean resetHwPnoList(); - - /** - * This returns whether HW PNO is supported or not. - * @param isConnectedPno Whether this is connected PNO vs disconnected PNO. - * @return true if HW PNO is supported, false otherwise. - */ - public abstract boolean isHwPnoSupported(boolean isConnectedPno); - - /** - * This returns whether a background scan should be running for HW PNO scan or not. - * @return true if background scan needs to be started, false otherwise. - */ - public abstract boolean shouldScheduleBackgroundScanForHwPno(); - - /** - * Set a new hotlist - */ - public abstract boolean setHotlist(WifiScanner.HotlistSettings settings, - WifiNative.HotlistEventHandler eventHandler); - - /** - * Reset any active hotlist - */ - public abstract void resetHotlist(); - - /** - * Start tracking significant wifi changes - */ - public abstract boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings, - WifiNative.SignificantWifiChangeEventHandler handler); - - /** - * Stop tracking significant wifi changes - */ - public abstract void untrackSignificantWifiChange(); -} diff --git a/service/java/com/android/server/wifi/WifiScanningScheduler.java b/service/java/com/android/server/wifi/WifiScanningScheduler.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/service/java/com/android/server/wifi/WifiScanningService.java b/service/java/com/android/server/wifi/WifiScanningService.java deleted file mode 100644 index f07954deb..000000000 --- a/service/java/com/android/server/wifi/WifiScanningService.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import android.content.Context; -import android.os.HandlerThread; -import android.util.Log; - -import com.android.server.SystemService; -import com.android.server.am.BatteryStatsService; - -public class WifiScanningService extends SystemService { - - static final String TAG = "WifiScanningService"; - private final WifiScanningServiceImpl mImpl; - private final HandlerThread mHandlerThread; - - public WifiScanningService(Context context) { - super(context); - Log.i(TAG, "Creating " + Context.WIFI_SCANNING_SERVICE); - mHandlerThread = new HandlerThread("WifiScanningService"); - mHandlerThread.start(); - mImpl = new WifiScanningServiceImpl(getContext(), mHandlerThread.getLooper(), - WifiScannerImpl.DEFAULT_FACTORY, BatteryStatsService.getService(), - WifiInjector.getInstance()); - } - - @Override - public void onStart() { - Log.i(TAG, "Publishing " + Context.WIFI_SCANNING_SERVICE); - publishBinderService(Context.WIFI_SCANNING_SERVICE, mImpl); - } - - @Override - public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { - Log.i(TAG, "Starting " + Context.WIFI_SCANNING_SERVICE); - mImpl.startService(); - } - } -} diff --git a/service/java/com/android/server/wifi/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/WifiScanningServiceImpl.java deleted file mode 100644 index 066729818..000000000 --- a/service/java/com/android/server/wifi/WifiScanningServiceImpl.java +++ /dev/null @@ -1,2599 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import android.Manifest; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.net.wifi.IWifiScanner; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiScanner; -import android.net.wifi.WifiScanner.BssidInfo; -import android.net.wifi.WifiScanner.ChannelSpec; -import android.net.wifi.WifiScanner.PnoSettings; -import android.net.wifi.WifiScanner.ScanData; -import android.net.wifi.WifiScanner.ScanSettings; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.os.WorkSource; -import android.util.ArrayMap; -import android.util.LocalLog; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.app.IBatteryStats; -import com.android.internal.util.AsyncChannel; -import com.android.internal.util.Protocol; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.server.wifi.scanner.BackgroundScanScheduler; -import com.android.server.wifi.scanner.ChannelHelper; -import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; -import com.android.server.wifi.scanner.ScanScheduleUtil; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -public class WifiScanningServiceImpl extends IWifiScanner.Stub { - - private static final String TAG = WifiScanningService.TAG; - private static final boolean DBG = false; - - private static final int MIN_PERIOD_PER_CHANNEL_MS = 200; // DFS needs 120 ms - private static final int UNKNOWN_PID = -1; - - private static final LocalLog mLocalLog = new LocalLog(1024); - - private final WifiMetrics mWifiMetrics; - - private static void localLog(String message) { - mLocalLog.log(message); - } - - private static void logw(String message) { - Log.w(TAG, message); - mLocalLog.log(message); - } - - private static void loge(String message) { - Log.e(TAG, message); - mLocalLog.log(message); - } - - private WifiScannerImpl mScannerImpl; - - @Override - public Messenger getMessenger() { - if (mClientHandler != null) { - return new Messenger(mClientHandler); - } else { - loge("WifiScanningServiceImpl trying to get messenger w/o initialization"); - return null; - } - } - - @Override - public Bundle getAvailableChannels(int band) { - mChannelHelper.updateChannels(); - ChannelSpec[] channelSpecs = mChannelHelper.getAvailableScanChannels(band); - ArrayList list = new ArrayList(channelSpecs.length); - for (ChannelSpec channelSpec : channelSpecs) { - list.add(channelSpec.frequency); - } - Bundle b = new Bundle(); - b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list); - return b; - } - - private void enforceLocationHardwarePermission(int uid) { - mContext.enforcePermission( - Manifest.permission.LOCATION_HARDWARE, - UNKNOWN_PID, uid, - "LocationHardware"); - } - - private class ClientHandler extends Handler { - - ClientHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); - if (client != null) { - logw("duplicate client connection: " + msg.sendingUid); - client.mChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED); - return; - } - - AsyncChannel ac = new AsyncChannel(); - ac.connected(mContext, this, msg.replyTo); - - client = new ExternalClientInfo(msg.sendingUid, msg.replyTo, ac); - client.register(); - - ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); - - if (DBG) Log.d(TAG, "client connected: " + client); - return; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); - if (client != null) { - client.mChannel.disconnect(); - } - return; - } - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { - ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); - if (client != null) { - if (DBG) { - Log.d(TAG, "client disconnected: " + client + ", reason: " + msg.arg1); - } - client.cleanup(); - } - return; - } - } - - try { - enforceLocationHardwarePermission(msg.sendingUid); - } catch (SecurityException e) { - localLog("failed to authorize app: " + e); - replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); - return; - } - - // Since this message is sent from WifiScanner using |sendMessageSynchronously| which - // doesn't set the correct |msg.replyTo| field. - if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) { - mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); - return; - } - - ClientInfo ci = mClients.get(msg.replyTo); - if (ci == null) { - loge("Could not find client info for message " + msg.replyTo); - replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener"); - return; - } - - switch (msg.what) { - case WifiScanner.CMD_START_BACKGROUND_SCAN: - case WifiScanner.CMD_STOP_BACKGROUND_SCAN: - case WifiScanner.CMD_SET_HOTLIST: - case WifiScanner.CMD_RESET_HOTLIST: - mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); - break; - case WifiScanner.CMD_START_PNO_SCAN: - case WifiScanner.CMD_STOP_PNO_SCAN: - mPnoScanStateMachine.sendMessage(Message.obtain(msg)); - break; - case WifiScanner.CMD_START_SINGLE_SCAN: - case WifiScanner.CMD_STOP_SINGLE_SCAN: - mSingleScanStateMachine.sendMessage(Message.obtain(msg)); - break; - case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: - case WifiScanner.CMD_START_TRACKING_CHANGE: - case WifiScanner.CMD_STOP_TRACKING_CHANGE: - mWifiChangeStateMachine.sendMessage(Message.obtain(msg)); - break; - default: - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "Invalid request"); - break; - } - } - } - - private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE; - - private static final int CMD_SCAN_RESULTS_AVAILABLE = BASE + 0; - private static final int CMD_FULL_SCAN_RESULTS = BASE + 1; - private static final int CMD_HOTLIST_AP_FOUND = BASE + 2; - private static final int CMD_HOTLIST_AP_LOST = BASE + 3; - private static final int CMD_WIFI_CHANGE_DETECTED = BASE + 4; - private static final int CMD_WIFI_CHANGE_TIMEOUT = BASE + 5; - private static final int CMD_DRIVER_LOADED = BASE + 6; - private static final int CMD_DRIVER_UNLOADED = BASE + 7; - private static final int CMD_SCAN_PAUSED = BASE + 8; - private static final int CMD_SCAN_RESTARTED = BASE + 9; - private static final int CMD_SCAN_FAILED = BASE + 10; - private static final int CMD_PNO_NETWORK_FOUND = BASE + 11; - private static final int CMD_PNO_SCAN_FAILED = BASE + 12; - - private final Context mContext; - private final Looper mLooper; - private final WifiScannerImpl.WifiScannerImplFactory mScannerImplFactory; - private final ArrayMap mClients; - - private ChannelHelper mChannelHelper; - private BackgroundScanScheduler mScheduler; - private WifiNative.ScanSettings mPreviousSchedule; - - private WifiBackgroundScanStateMachine mBackgroundScanStateMachine; - private WifiSingleScanStateMachine mSingleScanStateMachine; - private WifiChangeStateMachine mWifiChangeStateMachine; - private WifiPnoScanStateMachine mPnoScanStateMachine; - private ClientHandler mClientHandler; - private final IBatteryStats mBatteryStats; - private final AlarmManager mAlarmManager; - - WifiScanningServiceImpl(Context context, Looper looper, - WifiScannerImpl.WifiScannerImplFactory scannerImplFactory, IBatteryStats batteryStats, - WifiInjector wifiInjector) { - mContext = context; - mLooper = looper; - mScannerImplFactory = scannerImplFactory; - mBatteryStats = batteryStats; - mClients = new ArrayMap<>(); - mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - mWifiMetrics = wifiInjector.getWifiMetrics(); - - mPreviousSchedule = null; - } - - public void startService() { - mClientHandler = new ClientHandler(mLooper); - mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper); - mWifiChangeStateMachine = new WifiChangeStateMachine(mLooper); - mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper); - mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper); - - mContext.registerReceiver( - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - int state = intent.getIntExtra( - WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED); - if (DBG) localLog("SCAN_AVAILABLE : " + state); - if (state == WifiManager.WIFI_STATE_ENABLED) { - mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_LOADED); - mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED); - mPnoScanStateMachine.sendMessage(CMD_DRIVER_LOADED); - } else if (state == WifiManager.WIFI_STATE_DISABLED) { - mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); - mSingleScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); - mPnoScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); - } - } - }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE)); - - mBackgroundScanStateMachine.start(); - mWifiChangeStateMachine.start(); - mSingleScanStateMachine.start(); - mPnoScanStateMachine.start(); - } - - /** - * A map of objects of a WifiScanner client and handler id to an object of type T - */ - private static class ClientHandlerMap extends HashMap, T> { - public T put(ClientInfo ci, int handler, T value) { - return put(Pair.create(ci, handler), value); - } - - public T get(ClientInfo ci, int handler) { - return get(Pair.create(ci, handler)); - } - - public T remove(ClientInfo ci, int handler) { - return remove(Pair.create(ci, handler)); - } - - public Collection getAllValues(ClientInfo ci) { - ArrayList list = new ArrayList<>(); - for (Map.Entry, T> entry : entrySet()) { - if (entry.getKey().first == ci) { - list.add(entry.getValue()); - } - } - return list; - } - - public void removeAll(ClientInfo ci) { - Iterator, T>> iter = entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry, T> entry = iter.next(); - if (entry.getKey().first == ci) { - iter.remove(); - } - } - } - - public Map getHandlerToValueMap(ClientInfo ci) { - Map handlerToValueMap = new HashMap<>(); - for (Map.Entry, T> entry : entrySet()) { - if (entry.getKey().first == ci) { - handlerToValueMap.put(entry.getKey().second, entry.getValue()); - } - } - return handlerToValueMap; - } - } - - private static boolean isWorkSourceValid(WorkSource workSource) { - return workSource != null && workSource.size() > 0 && workSource.get(0) >= 0; - } - - private static WorkSource computeWorkSource(ClientInfo ci, WorkSource requestedWorkSource) { - if (requestedWorkSource != null) { - if (isWorkSourceValid(requestedWorkSource)) { - // Wifi currently doesn't use names, so need to clear names out of the - // supplied WorkSource to allow future WorkSource combining. - requestedWorkSource.clearNames(); - return requestedWorkSource; - } else { - loge("Got invalid work source request: " + requestedWorkSource.toString() + - " from " + ci); - } - } - WorkSource callingWorkSource = new WorkSource(ci.getUid()); - if (isWorkSourceValid(callingWorkSource)) { - return callingWorkSource; - } else { - loge("Client has invalid work source: " + callingWorkSource); - return new WorkSource(); - } - } - - private static class RequestInfo { - final ClientInfo clientInfo; - final int handlerId; - final WorkSource workSource; - final T settings; - - RequestInfo(ClientInfo clientInfo, int handlerId, WorkSource requestedWorkSource, - T settings) { - this.clientInfo = clientInfo; - this.handlerId = handlerId; - this.settings = settings; - this.workSource = computeWorkSource(clientInfo, requestedWorkSource); - } - - void reportEvent(int what, int arg1, Object obj) { - clientInfo.reportEvent(what, arg1, handlerId, obj); - } - } - - private static class RequestList extends ArrayList> { - void removeAllForHandler(ClientInfo ci, int handlerId) { - Iterator> iter = iterator(); - while (iter.hasNext()) { - RequestInfo entry = iter.next(); - if (entry.clientInfo == ci && entry.handlerId == handlerId) { - iter.remove(); - } - } - } - - void removeAllForClient(ClientInfo ci) { - Iterator> iter = iterator(); - while (iter.hasNext()) { - RequestInfo entry = iter.next(); - if (entry.clientInfo == ci) { - iter.remove(); - } - } - } - - WorkSource createMergedWorkSource() { - WorkSource mergedSource = new WorkSource(); - for (RequestInfo entry : this) { - mergedSource.add(entry.workSource); - } - return mergedSource; - } - } - - /** - * State machine that holds the state of single scans. Scans should only be active in the - * ScanningState. The pending scans and active scans maps are swaped when entering - * ScanningState. Any requests queued while scanning will be placed in the pending queue and - * executed after transitioning back to IdleState. - */ - class WifiSingleScanStateMachine extends StateMachine implements WifiNative.ScanEventHandler { - private final DefaultState mDefaultState = new DefaultState(); - private final DriverStartedState mDriverStartedState = new DriverStartedState(); - private final IdleState mIdleState = new IdleState(); - private final ScanningState mScanningState = new ScanningState(); - - private RequestList mActiveScans = new RequestList<>(); - private RequestList mPendingScans = new RequestList<>(); - - WifiSingleScanStateMachine(Looper looper) { - super("WifiSingleScanStateMachine", looper); - - setLogRecSize(128); - setLogOnlyTransitions(false); - - // CHECKSTYLE:OFF IndentationCheck - addState(mDefaultState); - addState(mDriverStartedState, mDefaultState); - addState(mIdleState, mDriverStartedState); - addState(mScanningState, mDriverStartedState); - // CHECKSTYLE:ON IndentationCheck - - setInitialState(mDefaultState); - } - - /** - * Called to indicate a change in state for the current scan. - * Will dispatch a coresponding event to the state machine - */ - @Override - public void onScanStatus(int event) { - if (DBG) localLog("onScanStatus event received, event=" + event); - switch(event) { - case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: - case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: - case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: - sendMessage(CMD_SCAN_RESULTS_AVAILABLE); - break; - case WifiNative.WIFI_SCAN_FAILED: - sendMessage(CMD_SCAN_FAILED); - break; - default: - Log.e(TAG, "Unknown scan status event: " + event); - break; - } - } - - /** - * Called for each full scan result if requested - */ - @Override - public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { - if (DBG) localLog("onFullScanResult received"); - sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); - } - - @Override - public void onScanPaused(ScanData[] scanData) { - // should not happen for single scan - Log.e(TAG, "Got scan paused for single scan"); - } - - @Override - public void onScanRestarted() { - // should not happen for single scan - Log.e(TAG, "Got scan restarted for single scan"); - } - - class DefaultState extends State { - @Override - public void enter() { - mActiveScans.clear(); - mPendingScans.clear(); - } - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_DRIVER_LOADED: - transitionTo(mIdleState); - return HANDLED; - case CMD_DRIVER_UNLOADED: - transitionTo(mDefaultState); - return HANDLED; - case WifiScanner.CMD_START_SINGLE_SCAN: - case WifiScanner.CMD_STOP_SINGLE_SCAN: - replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); - return HANDLED; - case CMD_SCAN_RESULTS_AVAILABLE: - if (DBG) localLog("ignored scan results available event"); - return HANDLED; - case CMD_FULL_SCAN_RESULTS: - if (DBG) localLog("ignored full scan result event"); - return HANDLED; - default: - return NOT_HANDLED; - } - - } - } - - /** - * State representing when the driver is running. This state is not meant to be transitioned - * directly, but is instead indented as a parent state of ScanningState and IdleState - * to hold common functionality and handle cleaning up scans when the driver is shut down. - */ - class DriverStartedState extends State { - @Override - public void exit() { - mWifiMetrics.incrementScanReturnEntry( - WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED, - mPendingScans.size()); - sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, - "Scan was interrupted"); - } - - @Override - public boolean processMessage(Message msg) { - ClientInfo ci = mClients.get(msg.replyTo); - - switch (msg.what) { - case WifiScanner.CMD_START_SINGLE_SCAN: - mWifiMetrics.incrementOneshotScanCount(); - Bundle scanParams = (Bundle) msg.obj; - if (scanParams == null) { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); - return HANDLED; - } - scanParams.setDefusable(true); - ScanSettings scanSettings = - scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); - WorkSource workSource = - scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); - if (validateAndAddToScanQueue(ci, msg.arg2, scanSettings, workSource)) { - replySucceeded(msg); - // If were not currently scanning then try to start a scan. Otherwise - // this scan will be scheduled when transitioning back to IdleState - // after finishing the current scan. - if (getCurrentState() != mScanningState) { - tryToStartNewScan(); - } - } else { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); - mWifiMetrics.incrementScanReturnEntry( - WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1); - } - return HANDLED; - case WifiScanner.CMD_STOP_SINGLE_SCAN: - removeSingleScanRequest(ci, msg.arg2); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - class IdleState extends State { - @Override - public void enter() { - tryToStartNewScan(); - } - - @Override - public boolean processMessage(Message msg) { - return NOT_HANDLED; - } - } - - class ScanningState extends State { - private WorkSource mScanWorkSource; - - @Override - public void enter() { - mScanWorkSource = mActiveScans.createMergedWorkSource(); - try { - mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource); - } catch (RemoteException e) { - loge(e.toString()); - } - } - - @Override - public void exit() { - try { - mBatteryStats.noteWifiScanStoppedFromSource(mScanWorkSource); - } catch (RemoteException e) { - loge(e.toString()); - } - - // if any scans are still active (never got results available then indicate failure) - mWifiMetrics.incrementScanReturnEntry( - WifiMetricsProto.WifiLog.SCAN_UNKNOWN, - mActiveScans.size()); - sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, - "Scan was interrupted"); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_SCAN_RESULTS_AVAILABLE: - mWifiMetrics.incrementScanReturnEntry( - WifiMetricsProto.WifiLog.SCAN_SUCCESS, - mActiveScans.size()); - reportScanResults(mScannerImpl.getLatestSingleScanResults()); - mActiveScans.clear(); - transitionTo(mIdleState); - return HANDLED; - case CMD_FULL_SCAN_RESULTS: - reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); - return HANDLED; - case CMD_SCAN_FAILED: - mWifiMetrics.incrementScanReturnEntry( - WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size()); - sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, - "Scan failed"); - transitionTo(mIdleState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - boolean validateAndAddToScanQueue(ClientInfo ci, int handler, ScanSettings settings, - WorkSource workSource) { - if (ci == null) { - Log.d(TAG, "Failing single scan request ClientInfo not found " + handler); - return false; - } - if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) { - if (settings.channels == null || settings.channels.length == 0) { - Log.d(TAG, "Failing single scan because channel list was empty"); - return false; - } - } - logScanRequest("addSingleScanRequest", ci, handler, workSource, settings, null); - mPendingScans.add(new RequestInfo(ci, handler, workSource, settings)); - return true; - } - - void removeSingleScanRequest(ClientInfo ci, int handler) { - if (ci != null) { - logScanRequest("removeSingleScanRequest", ci, handler, null, null, null); - mPendingScans.removeAllForHandler(ci, handler); - mActiveScans.removeAllForHandler(ci, handler); - } - } - - void removeSingleScanRequests(ClientInfo ci) { - if (ci != null) { - logScanRequest("removeSingleScanRequests", ci, -1, null, null, null); - mPendingScans.removeAllForClient(ci); - mActiveScans.removeAllForClient(ci); - } - } - - void tryToStartNewScan() { - if (mPendingScans.size() == 0) { // no pending requests - return; - } - mChannelHelper.updateChannels(); - // TODO move merging logic to a scheduler - WifiNative.ScanSettings settings = new WifiNative.ScanSettings(); - settings.num_buckets = 1; - WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings(); - bucketSettings.bucket = 0; - bucketSettings.period_ms = 0; - bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; - - ChannelCollection channels = mChannelHelper.createChannelCollection(); - HashSet hiddenNetworkIdSet = new HashSet<>(); - for (RequestInfo entry : mPendingScans) { - channels.addChannels(entry.settings); - if (entry.settings.hiddenNetworkIds != null) { - for (int i = 0; i < entry.settings.hiddenNetworkIds.length; i++) { - hiddenNetworkIdSet.add(entry.settings.hiddenNetworkIds[i]); - } - } - if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) - != 0) { - bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; - } - } - if (hiddenNetworkIdSet.size() > 0) { - settings.hiddenNetworkIds = new int[hiddenNetworkIdSet.size()]; - int numHiddenNetworks = 0; - for (Integer hiddenNetworkId : hiddenNetworkIdSet) { - settings.hiddenNetworkIds[numHiddenNetworks++] = hiddenNetworkId; - } - } - - channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE); - - settings.buckets = new WifiNative.BucketSettings[] {bucketSettings}; - if (mScannerImpl.startSingleScan(settings, this)) { - // swap pending and active scan requests - RequestList tmp = mActiveScans; - mActiveScans = mPendingScans; - mPendingScans = tmp; - // make sure that the pending list is clear - mPendingScans.clear(); - transitionTo(mScanningState); - } else { - mWifiMetrics.incrementScanReturnEntry( - WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size()); - // notify and cancel failed scans - sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, - "Failed to start single scan"); - } - } - - void sendOpFailedToAllAndClear(RequestList clientHandlers, int reason, - String description) { - for (RequestInfo entry : clientHandlers) { - entry.reportEvent(WifiScanner.CMD_OP_FAILED, 0, - new WifiScanner.OperationResult(reason, description)); - } - clientHandlers.clear(); - } - - void reportFullScanResult(ScanResult result, int bucketsScanned) { - for (RequestInfo entry : mActiveScans) { - if (ScanScheduleUtil.shouldReportFullScanResultForSettings(mChannelHelper, - result, bucketsScanned, entry.settings, -1)) { - entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result); - } - } - } - - void reportScanResults(ScanData results) { - if (results != null && results.getResults() != null) { - if (results.getResults().length > 0) { - mWifiMetrics.incrementNonEmptyScanResultCount(); - } else { - mWifiMetrics.incrementEmptyScanResultCount(); - } - } - for (RequestInfo entry : mActiveScans) { - ScanData[] resultsArray = new ScanData[] {results}; - ScanData[] resultsToDeliver = ScanScheduleUtil.filterResultsForSettings( - mChannelHelper, resultsArray, entry.settings, -1); - WifiScanner.ParcelableScanData parcelableScanData = - new WifiScanner.ParcelableScanData(resultsToDeliver); - logCallback("singleScanResults", entry.clientInfo, entry.handlerId); - entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableScanData); - // make sure the handler is removed - entry.reportEvent(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, null); - } - } - } - - class WifiBackgroundScanStateMachine extends StateMachine - implements WifiNative.ScanEventHandler, WifiNative.HotlistEventHandler { - - private final DefaultState mDefaultState = new DefaultState(); - private final StartedState mStartedState = new StartedState(); - private final PausedState mPausedState = new PausedState(); - - private final ClientHandlerMap mActiveBackgroundScans = - new ClientHandlerMap() { - @Override - public ScanSettings put(ClientInfo ci, int handler, ScanSettings value) { - ScanSettings settings = put(Pair.create(ci, handler), value); - ci.reportScanWorkUpdate(); - return settings; - } - - @Override - public ScanSettings remove(ClientInfo ci, int handler) { - ScanSettings settings = remove(Pair.create(ci, handler)); - ci.reportScanWorkUpdate(); - return settings; - } - }; - private final ClientHandlerMap mActiveHotlistSettings = - new ClientHandlerMap<>(); - - WifiBackgroundScanStateMachine(Looper looper) { - super(TAG, looper); - - setLogRecSize(512); - setLogOnlyTransitions(false); - - // CHECKSTYLE:OFF IndentationCheck - addState(mDefaultState); - addState(mStartedState, mDefaultState); - addState(mPausedState, mDefaultState); - // CHECKSTYLE:ON IndentationCheck - - setInitialState(mDefaultState); - } - - public Collection getBackgroundScanSettings(ClientInfo ci) { - return mActiveBackgroundScans.getAllValues(ci); - } - - public Map getBackgroundScanSettingsHandlerMap(ClientInfo ci) { - return mActiveBackgroundScans.getHandlerToValueMap(ci); - } - - public void removeBackgroundScanSettings(ClientInfo ci) { - mActiveBackgroundScans.removeAll(ci); - updateSchedule(); - } - - public void removeHotlistSettings(ClientInfo ci) { - mActiveHotlistSettings.removeAll(ci); - resetHotlist(); - } - - @Override - public void onScanStatus(int event) { - if (DBG) localLog("onScanStatus event received, event=" + event); - switch(event) { - case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: - case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: - case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: - sendMessage(CMD_SCAN_RESULTS_AVAILABLE); - break; - case WifiNative.WIFI_SCAN_FAILED: - sendMessage(CMD_SCAN_FAILED); - break; - default: - Log.e(TAG, "Unknown scan status event: " + event); - break; - } - } - - @Override - public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { - if (DBG) localLog("onFullScanResult received"); - sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); - } - - @Override - public void onScanPaused(ScanData scanData[]) { - if (DBG) localLog("onScanPaused received"); - sendMessage(CMD_SCAN_PAUSED, scanData); - } - - @Override - public void onScanRestarted() { - if (DBG) localLog("onScanRestarted received"); - sendMessage(CMD_SCAN_RESTARTED); - } - - @Override - public void onHotlistApFound(ScanResult[] results) { - if (DBG) localLog("onHotlistApFound event received"); - sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results); - } - - @Override - public void onHotlistApLost(ScanResult[] results) { - if (DBG) localLog("onHotlistApLost event received"); - sendMessage(CMD_HOTLIST_AP_LOST, 0, 0, results); - } - - class DefaultState extends State { - @Override - public void enter() { - if (DBG) localLog("DefaultState"); - mActiveBackgroundScans.clear(); - mActiveHotlistSettings.clear(); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_DRIVER_LOADED: - // TODO this should be moved to a common location since it is used outside - // of this state machine. It is ok right now because the driver loaded event - // is sent to this state machine first. - if (mScannerImpl == null) { - mScannerImpl = mScannerImplFactory.create(mContext, mLooper); - mChannelHelper = mScannerImpl.getChannelHelper(); - } - - mScheduler = new BackgroundScanScheduler(mChannelHelper); - - WifiNative.ScanCapabilities capabilities = - new WifiNative.ScanCapabilities(); - if (!mScannerImpl.getScanCapabilities(capabilities)) { - loge("could not get scan capabilities"); - return HANDLED; - } - mScheduler.setMaxBuckets(capabilities.max_scan_buckets); - mScheduler.setMaxApPerScan(capabilities.max_ap_cache_per_scan); - - Log.i(TAG, "wifi driver loaded with scan capabilities: " - + "max buckets=" + capabilities.max_scan_buckets); - - transitionTo(mStartedState); - return HANDLED; - case CMD_DRIVER_UNLOADED: - Log.i(TAG, "wifi driver unloaded"); - transitionTo(mDefaultState); - break; - case WifiScanner.CMD_START_BACKGROUND_SCAN: - case WifiScanner.CMD_STOP_BACKGROUND_SCAN: - case WifiScanner.CMD_START_SINGLE_SCAN: - case WifiScanner.CMD_STOP_SINGLE_SCAN: - case WifiScanner.CMD_SET_HOTLIST: - case WifiScanner.CMD_RESET_HOTLIST: - case WifiScanner.CMD_GET_SCAN_RESULTS: - replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); - break; - - case CMD_SCAN_RESULTS_AVAILABLE: - if (DBG) localLog("ignored scan results available event"); - break; - - case CMD_FULL_SCAN_RESULTS: - if (DBG) localLog("ignored full scan result event"); - break; - - default: - break; - } - - return HANDLED; - } - } - - class StartedState extends State { - - @Override - public void enter() { - if (DBG) localLog("StartedState"); - } - - @Override - public void exit() { - sendBackgroundScanFailedToAllAndClear( - WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); - sendHotlistFailedToAllAndClear( - WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); - mScannerImpl.cleanup(); - } - - @Override - public boolean processMessage(Message msg) { - ClientInfo ci = mClients.get(msg.replyTo); - - switch (msg.what) { - case CMD_DRIVER_LOADED: - return NOT_HANDLED; - case CMD_DRIVER_UNLOADED: - return NOT_HANDLED; - case WifiScanner.CMD_START_BACKGROUND_SCAN: { - mWifiMetrics.incrementBackgroundScanCount(); - Bundle scanParams = (Bundle) msg.obj; - if (scanParams == null) { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); - return HANDLED; - } - scanParams.setDefusable(true); - ScanSettings scanSettings = - scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); - WorkSource workSource = - scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); - if (addBackgroundScanRequest(ci, msg.arg2, scanSettings, workSource)) { - replySucceeded(msg); - } else { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); - } - break; - } - case WifiScanner.CMD_STOP_BACKGROUND_SCAN: - removeBackgroundScanRequest(ci, msg.arg2); - break; - case WifiScanner.CMD_GET_SCAN_RESULTS: - reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); - replySucceeded(msg); - break; - case WifiScanner.CMD_SET_HOTLIST: - addHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj); - replySucceeded(msg); - break; - case WifiScanner.CMD_RESET_HOTLIST: - removeHotlist(ci, msg.arg2); - break; - case CMD_SCAN_RESULTS_AVAILABLE: - reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); - break; - case CMD_FULL_SCAN_RESULTS: - reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); - break; - case CMD_HOTLIST_AP_FOUND: - reportHotlistResults(WifiScanner.CMD_AP_FOUND, (ScanResult[]) msg.obj); - break; - case CMD_HOTLIST_AP_LOST: - reportHotlistResults(WifiScanner.CMD_AP_LOST, (ScanResult[]) msg.obj); - break; - case CMD_SCAN_PAUSED: - reportScanResults((ScanData[]) msg.obj); - transitionTo(mPausedState); - break; - case CMD_SCAN_FAILED: - Log.e(TAG, "WifiScanner background scan gave CMD_SCAN_FAILED"); - sendBackgroundScanFailedToAllAndClear( - WifiScanner.REASON_UNSPECIFIED, "Background Scan failed"); - break; - default: - return NOT_HANDLED; - } - - return HANDLED; - } - } - - class PausedState extends State { - @Override - public void enter() { - if (DBG) localLog("PausedState"); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_SCAN_RESTARTED: - transitionTo(mStartedState); - break; - default: - deferMessage(msg); - break; - } - return HANDLED; - } - } - - private boolean addBackgroundScanRequest(ClientInfo ci, int handler, - ScanSettings settings, WorkSource workSource) { - // sanity check the input - if (ci == null) { - Log.d(TAG, "Failing scan request ClientInfo not found " + handler); - return false; - } - if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) { - loge("Failing scan request because periodInMs is " + settings.periodInMs - + ", min scan period is: " + WifiScanner.MIN_SCAN_PERIOD_MS); - return false; - } - - if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED && settings.channels == null) { - loge("Channels was null with unspecified band"); - return false; - } - - if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED - && settings.channels.length == 0) { - loge("No channels specified"); - return false; - } - - int minSupportedPeriodMs = mChannelHelper.estimateScanDuration(settings); - if (settings.periodInMs < minSupportedPeriodMs) { - loge("Failing scan request because minSupportedPeriodMs is " - + minSupportedPeriodMs + " but the request wants " + settings.periodInMs); - return false; - } - - // check truncated binary exponential back off scan settings - if (settings.maxPeriodInMs != 0 && settings.maxPeriodInMs != settings.periodInMs) { - if (settings.maxPeriodInMs < settings.periodInMs) { - loge("Failing scan request because maxPeriodInMs is " + settings.maxPeriodInMs - + " but less than periodInMs " + settings.periodInMs); - return false; - } - if (settings.maxPeriodInMs > WifiScanner.MAX_SCAN_PERIOD_MS) { - loge("Failing scan request because maxSupportedPeriodMs is " - + WifiScanner.MAX_SCAN_PERIOD_MS + " but the request wants " - + settings.maxPeriodInMs); - return false; - } - if (settings.stepCount < 1) { - loge("Failing scan request because stepCount is " + settings.stepCount - + " which is less than 1"); - return false; - } - } - - logScanRequest("addBackgroundScanRequest", ci, handler, null, settings, null); - // TODO(b/27903217): Blame scan on provided work source - mActiveBackgroundScans.put(ci, handler, settings); - - if (updateSchedule()) { - return true; - } else { - mActiveBackgroundScans.remove(ci, handler); - localLog("Failing scan request because failed to reset scan"); - return false; - } - } - - private boolean updateSchedule() { - mChannelHelper.updateChannels(); - Collection settings = mActiveBackgroundScans.values(); - - mScheduler.updateSchedule(settings); - WifiNative.ScanSettings schedule = mScheduler.getSchedule(); - - if (ScanScheduleUtil.scheduleEquals(mPreviousSchedule, schedule)) { - if (DBG) Log.d(TAG, "schedule updated with no change"); - return true; - } - - mPreviousSchedule = schedule; - - if (schedule.num_buckets == 0) { - mScannerImpl.stopBatchedScan(); - if (DBG) Log.d(TAG, "scan stopped"); - return true; - } else { - Log.d(TAG, "starting scan: " - + "base period=" + schedule.base_period_ms - + ", max ap per scan=" + schedule.max_ap_per_scan - + ", batched scans=" + schedule.report_threshold_num_scans); - for (int b = 0; b < schedule.num_buckets; b++) { - WifiNative.BucketSettings bucket = schedule.buckets[b]; - Log.d(TAG, "bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" - + "[" + bucket.report_events + "]: " - + ChannelHelper.toString(bucket)); - } - - if (mScannerImpl.startBatchedScan(schedule, this)) { - if (DBG) { - Log.d(TAG, "scan restarted with " + schedule.num_buckets - + " bucket(s) and base period: " + schedule.base_period_ms); - } - return true; - } else { - mPreviousSchedule = null; - loge("error starting scan: " - + "base period=" + schedule.base_period_ms - + ", max ap per scan=" + schedule.max_ap_per_scan - + ", batched scans=" + schedule.report_threshold_num_scans); - for (int b = 0; b < schedule.num_buckets; b++) { - WifiNative.BucketSettings bucket = schedule.buckets[b]; - loge("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" - + "[" + bucket.report_events + "]: " - + ChannelHelper.toString(bucket)); - } - return false; - } - } - } - - private void removeBackgroundScanRequest(ClientInfo ci, int handler) { - if (ci != null) { - ScanSettings settings = mActiveBackgroundScans.remove(ci, handler); - logScanRequest("removeBackgroundScanRequest", ci, handler, null, settings, null); - updateSchedule(); - } - } - - private void reportFullScanResult(ScanResult result, int bucketsScanned) { - for (Map.Entry, ScanSettings> entry - : mActiveBackgroundScans.entrySet()) { - ClientInfo ci = entry.getKey().first; - int handler = entry.getKey().second; - ScanSettings settings = entry.getValue(); - if (mScheduler.shouldReportFullScanResultForSettings( - result, bucketsScanned, settings)) { - ScanResult newResult = new ScanResult(result); - if (result.informationElements != null) { - newResult.informationElements = result.informationElements.clone(); - } - else { - newResult.informationElements = null; - } - ci.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, handler, newResult); - } - } - } - - private void reportScanResults(ScanData[] results) { - for (ScanData result : results) { - if (result != null && result.getResults() != null) { - if (result.getResults().length > 0) { - mWifiMetrics.incrementNonEmptyScanResultCount(); - } else { - mWifiMetrics.incrementEmptyScanResultCount(); - } - } - } - for (Map.Entry, ScanSettings> entry - : mActiveBackgroundScans.entrySet()) { - ClientInfo ci = entry.getKey().first; - int handler = entry.getKey().second; - ScanSettings settings = entry.getValue(); - ScanData[] resultsToDeliver = - mScheduler.filterResultsForSettings(results, settings); - if (resultsToDeliver != null) { - logCallback("backgroundScanResults", ci, handler); - WifiScanner.ParcelableScanData parcelableScanData = - new WifiScanner.ParcelableScanData(resultsToDeliver); - ci.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanData); - } - } - } - - private void sendBackgroundScanFailedToAllAndClear(int reason, String description) { - for (Pair key : mActiveBackgroundScans.keySet()) { - ClientInfo ci = key.first; - int handler = key.second; - ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, - new WifiScanner.OperationResult(reason, description)); - } - mActiveBackgroundScans.clear(); - } - - private void addHotlist(ClientInfo ci, int handler, WifiScanner.HotlistSettings settings) { - mActiveHotlistSettings.put(ci, handler, settings); - resetHotlist(); - } - - private void removeHotlist(ClientInfo ci, int handler) { - mActiveHotlistSettings.remove(ci, handler); - resetHotlist(); - } - - private void resetHotlist() { - Collection settings = mActiveHotlistSettings.values(); - int num_hotlist_ap = 0; - - for (WifiScanner.HotlistSettings s : settings) { - num_hotlist_ap += s.bssidInfos.length; - } - - if (num_hotlist_ap == 0) { - mScannerImpl.resetHotlist(); - } else { - BssidInfo[] bssidInfos = new BssidInfo[num_hotlist_ap]; - int apLostThreshold = Integer.MAX_VALUE; - int index = 0; - for (WifiScanner.HotlistSettings s : settings) { - for (int i = 0; i < s.bssidInfos.length; i++, index++) { - bssidInfos[index] = s.bssidInfos[i]; - } - if (s.apLostThreshold < apLostThreshold) { - apLostThreshold = s.apLostThreshold; - } - } - - WifiScanner.HotlistSettings mergedSettings = new WifiScanner.HotlistSettings(); - mergedSettings.bssidInfos = bssidInfos; - mergedSettings.apLostThreshold = apLostThreshold; - mScannerImpl.setHotlist(mergedSettings, this); - } - } - - private void reportHotlistResults(int what, ScanResult[] results) { - if (DBG) localLog("reportHotlistResults " + what + " results " + results.length); - for (Map.Entry, WifiScanner.HotlistSettings> entry - : mActiveHotlistSettings.entrySet()) { - ClientInfo ci = entry.getKey().first; - int handler = entry.getKey().second; - WifiScanner.HotlistSettings settings = entry.getValue(); - int num_results = 0; - for (ScanResult result : results) { - for (BssidInfo BssidInfo : settings.bssidInfos) { - if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) { - num_results++; - break; - } - } - } - if (num_results == 0) { - // nothing to report - return; - } - ScanResult[] results2 = new ScanResult[num_results]; - int index = 0; - for (ScanResult result : results) { - for (BssidInfo BssidInfo : settings.bssidInfos) { - if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) { - results2[index] = result; - index++; - } - } - } - WifiScanner.ParcelableScanResults parcelableScanResults = - new WifiScanner.ParcelableScanResults(results2); - - ci.reportEvent(what, 0, handler, parcelableScanResults); - } - } - - private void sendHotlistFailedToAllAndClear(int reason, String description) { - for (Pair key : mActiveHotlistSettings.keySet()) { - ClientInfo ci = key.first; - int handler = key.second; - ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, - new WifiScanner.OperationResult(reason, description)); - } - mActiveHotlistSettings.clear(); - } - } - - /** - * PNO scan state machine has 5 states: - * -Default State - * -Started State - * -Hw Pno Scan state - * -Single Scan state - * -Sw Pno Scan state - * - * These are the main state transitions: - * 1. Start at |Default State| - * 2. Move to |Started State| when we get the |WIFI_SCAN_AVAILABLE| broadcast from WifiManager. - * 3. When a new PNO scan request comes in: - * a.1. Switch to |Hw Pno Scan state| when the device supports HW PNO - * (This could either be HAL based ePNO or supplicant based PNO). - * a.2. In |Hw Pno Scan state| when PNO scan results are received, check if the result - * contains IE (information elements). If yes, send the results to the client, else - * switch to |Single Scan state| and send the result to the client when the scan result - * is obtained. - * b.1. Switch to |Sw Pno Scan state| when the device does not supports HW PNO - * (This is for older devices which do not support HW PNO and for connected PNO on - * devices which support supplicant based PNO) - * b.2. In |Sw Pno Scan state| send the result to the client when the background scan result - * is obtained - * - * Note: PNO scans only work for a single client today. We don't have support in HW to support - * multiple requests at the same time, so will need non-trivial changes to support (if at all - * possible) in WifiScanningService. - */ - class WifiPnoScanStateMachine extends StateMachine implements WifiNative.PnoEventHandler { - - private final DefaultState mDefaultState = new DefaultState(); - private final StartedState mStartedState = new StartedState(); - private final HwPnoScanState mHwPnoScanState = new HwPnoScanState(); - private final SwPnoScanState mSwPnoScanState = new SwPnoScanState(); - private final SingleScanState mSingleScanState = new SingleScanState(); - private InternalClientInfo mInternalClientInfo; - - private final ClientHandlerMap> mActivePnoScans = - new ClientHandlerMap<>(); - - WifiPnoScanStateMachine(Looper looper) { - super(TAG, looper); - - setLogRecSize(512); - setLogOnlyTransitions(false); - - // CHECKSTYLE:OFF IndentationCheck - addState(mDefaultState); - addState(mStartedState, mDefaultState); - addState(mHwPnoScanState, mStartedState); - addState(mSingleScanState, mHwPnoScanState); - addState(mSwPnoScanState, mStartedState); - // CHECKSTYLE:ON IndentationCheck - - setInitialState(mDefaultState); - } - - public void removePnoSettings(ClientInfo ci) { - mActivePnoScans.removeAll(ci); - transitionTo(mStartedState); - } - - @Override - public void onPnoNetworkFound(ScanResult[] results) { - if (DBG) localLog("onWifiPnoNetworkFound event received"); - sendMessage(CMD_PNO_NETWORK_FOUND, 0, 0, results); - } - - @Override - public void onPnoScanFailed() { - if (DBG) localLog("onWifiPnoScanFailed event received"); - sendMessage(CMD_PNO_SCAN_FAILED, 0, 0, null); - } - - class DefaultState extends State { - @Override - public void enter() { - if (DBG) localLog("DefaultState"); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_DRIVER_LOADED: - transitionTo(mStartedState); - break; - case CMD_DRIVER_UNLOADED: - transitionTo(mDefaultState); - break; - case WifiScanner.CMD_START_PNO_SCAN: - case WifiScanner.CMD_STOP_PNO_SCAN: - replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); - break; - case CMD_PNO_NETWORK_FOUND: - case CMD_PNO_SCAN_FAILED: - case WifiScanner.CMD_SCAN_RESULT: - case WifiScanner.CMD_OP_FAILED: - loge("Unexpected message " + msg.what); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class StartedState extends State { - @Override - public void enter() { - if (DBG) localLog("StartedState"); - } - - @Override - public void exit() { - sendPnoScanFailedToAllAndClear( - WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); - } - - @Override - public boolean processMessage(Message msg) { - ClientInfo ci = mClients.get(msg.replyTo); - switch (msg.what) { - case WifiScanner.CMD_START_PNO_SCAN: - Bundle pnoParams = (Bundle) msg.obj; - if (pnoParams == null) { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); - return HANDLED; - } - pnoParams.setDefusable(true); - PnoSettings pnoSettings = - pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); - // This message is handled after the transition to SwPnoScan/HwPnoScan state - deferMessage(msg); - if (mScannerImpl.isHwPnoSupported(pnoSettings.isConnected)) { - transitionTo(mHwPnoScanState); - } else { - transitionTo(mSwPnoScanState); - } - break; - case WifiScanner.CMD_STOP_PNO_SCAN: - replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "no scan running"); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class HwPnoScanState extends State { - @Override - public void enter() { - if (DBG) localLog("HwPnoScanState"); - } - - @Override - public void exit() { - // Reset PNO scan in ScannerImpl before we exit. - mScannerImpl.resetHwPnoList(); - removeInternalClient(); - } - - @Override - public boolean processMessage(Message msg) { - ClientInfo ci = mClients.get(msg.replyTo); - switch (msg.what) { - case WifiScanner.CMD_START_PNO_SCAN: - Bundle pnoParams = (Bundle) msg.obj; - if (pnoParams == null) { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); - return HANDLED; - } - pnoParams.setDefusable(true); - PnoSettings pnoSettings = - pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); - ScanSettings scanSettings = - pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY); - if (addHwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) { - replySucceeded(msg); - } else { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); - transitionTo(mStartedState); - } - break; - case WifiScanner.CMD_STOP_PNO_SCAN: - removeHwPnoScanRequest(ci, msg.arg2); - transitionTo(mStartedState); - break; - case CMD_PNO_NETWORK_FOUND: - ScanResult[] scanResults = ((ScanResult[]) msg.obj); - if (isSingleScanNeeded(scanResults)) { - addSingleScanRequest(getScanSettings()); - transitionTo(mSingleScanState); - } else { - reportPnoNetworkFound((ScanResult[]) msg.obj); - } - break; - case CMD_PNO_SCAN_FAILED: - sendPnoScanFailedToAllAndClear( - WifiScanner.REASON_UNSPECIFIED, "pno scan failed"); - transitionTo(mStartedState); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class SingleScanState extends State { - @Override - public void enter() { - if (DBG) localLog("SingleScanState"); - } - - @Override - public boolean processMessage(Message msg) { - ClientInfo ci = mClients.get(msg.replyTo); - switch (msg.what) { - case WifiScanner.CMD_SCAN_RESULT: - WifiScanner.ParcelableScanData parcelableScanData = - (WifiScanner.ParcelableScanData) msg.obj; - ScanData[] scanDatas = parcelableScanData.getResults(); - ScanData lastScanData = scanDatas[scanDatas.length - 1]; - reportPnoNetworkFound(lastScanData.getResults()); - transitionTo(mHwPnoScanState); - break; - case WifiScanner.CMD_OP_FAILED: - sendPnoScanFailedToAllAndClear( - WifiScanner.REASON_UNSPECIFIED, "single scan failed"); - transitionTo(mStartedState); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class SwPnoScanState extends State { - private final ArrayList mSwPnoFullScanResults = new ArrayList<>(); - - @Override - public void enter() { - if (DBG) localLog("SwPnoScanState"); - mSwPnoFullScanResults.clear(); - } - - @Override - public void exit() { - removeInternalClient(); - } - - @Override - public boolean processMessage(Message msg) { - ClientInfo ci = mClients.get(msg.replyTo); - switch (msg.what) { - case WifiScanner.CMD_START_PNO_SCAN: - Bundle pnoParams = (Bundle) msg.obj; - if (pnoParams == null) { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); - return HANDLED; - } - pnoParams.setDefusable(true); - PnoSettings pnoSettings = - pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); - ScanSettings scanSettings = - pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY); - if (addSwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) { - replySucceeded(msg); - } else { - replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); - transitionTo(mStartedState); - } - break; - case WifiScanner.CMD_STOP_PNO_SCAN: - removeSwPnoScanRequest(ci, msg.arg2); - transitionTo(mStartedState); - break; - case WifiScanner.CMD_FULL_SCAN_RESULT: - // Aggregate full scan results until we get the |CMD_SCAN_RESULT| message - mSwPnoFullScanResults.add((ScanResult) msg.obj); - break; - case WifiScanner.CMD_SCAN_RESULT: - ScanResult[] scanResults = mSwPnoFullScanResults.toArray( - new ScanResult[mSwPnoFullScanResults.size()]); - reportPnoNetworkFound(scanResults); - mSwPnoFullScanResults.clear(); - break; - case WifiScanner.CMD_OP_FAILED: - sendPnoScanFailedToAllAndClear( - WifiScanner.REASON_UNSPECIFIED, "background scan failed"); - transitionTo(mStartedState); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - private WifiNative.PnoSettings convertPnoSettingsToNative(PnoSettings pnoSettings) { - WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings(); - nativePnoSetting.min5GHzRssi = pnoSettings.min5GHzRssi; - nativePnoSetting.min24GHzRssi = pnoSettings.min24GHzRssi; - nativePnoSetting.initialScoreMax = pnoSettings.initialScoreMax; - nativePnoSetting.currentConnectionBonus = pnoSettings.currentConnectionBonus; - nativePnoSetting.sameNetworkBonus = pnoSettings.sameNetworkBonus; - nativePnoSetting.secureBonus = pnoSettings.secureBonus; - nativePnoSetting.band5GHzBonus = pnoSettings.band5GHzBonus; - nativePnoSetting.isConnected = pnoSettings.isConnected; - nativePnoSetting.networkList = - new WifiNative.PnoNetwork[pnoSettings.networkList.length]; - for (int i = 0; i < pnoSettings.networkList.length; i++) { - nativePnoSetting.networkList[i] = new WifiNative.PnoNetwork(); - nativePnoSetting.networkList[i].ssid = pnoSettings.networkList[i].ssid; - nativePnoSetting.networkList[i].networkId = pnoSettings.networkList[i].networkId; - nativePnoSetting.networkList[i].priority = pnoSettings.networkList[i].priority; - nativePnoSetting.networkList[i].flags = pnoSettings.networkList[i].flags; - nativePnoSetting.networkList[i].auth_bit_field = - pnoSettings.networkList[i].authBitField; - } - return nativePnoSetting; - } - - // Retrieve the active PNO settings. - private PnoSettings getPnoSettings() { - return mActivePnoScans.entrySet().iterator().next().getValue().first; - } - - // Retrieve the active scan settings. - private ScanSettings getScanSettings() { - return mActivePnoScans.entrySet().iterator().next().getValue().second; - } - - private void removeInternalClient() { - if (mInternalClientInfo != null) { - mInternalClientInfo.cleanup(); - mInternalClientInfo = null; - } else { - Log.w(TAG, "No Internal client for PNO"); - } - } - - private void addInternalClient(ClientInfo ci) { - if (mInternalClientInfo == null) { - mInternalClientInfo = - new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler())); - mInternalClientInfo.register(); - } else { - Log.w(TAG, "Internal client for PNO already exists"); - } - } - - private void addPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, - PnoSettings pnoSettings) { - mActivePnoScans.put(ci, handler, Pair.create(pnoSettings, scanSettings)); - addInternalClient(ci); - } - - private Pair removePnoScanRequest(ClientInfo ci, int handler) { - Pair settings = mActivePnoScans.remove(ci, handler); - return settings; - } - - private boolean addHwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, - PnoSettings pnoSettings) { - if (ci == null) { - Log.d(TAG, "Failing scan request ClientInfo not found " + handler); - return false; - } - if (!mActivePnoScans.isEmpty()) { - loge("Failing scan request because there is already an active scan"); - return false; - } - WifiNative.PnoSettings nativePnoSettings = convertPnoSettingsToNative(pnoSettings); - if (!mScannerImpl.setHwPnoList(nativePnoSettings, mPnoScanStateMachine)) { - return false; - } - logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings); - addPnoScanRequest(ci, handler, scanSettings, pnoSettings); - // HW PNO is supported, check if we need a background scan running for this. - if (mScannerImpl.shouldScheduleBackgroundScanForHwPno()) { - addBackgroundScanRequest(scanSettings); - } - return true; - } - - private void removeHwPnoScanRequest(ClientInfo ci, int handler) { - if (ci != null) { - Pair settings = removePnoScanRequest(ci, handler); - logScanRequest("removeHwPnoScanRequest", ci, handler, null, - settings.second, settings.first); - } - } - - private boolean addSwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, - PnoSettings pnoSettings) { - if (ci == null) { - Log.d(TAG, "Failing scan request ClientInfo not found " + handler); - return false; - } - if (!mActivePnoScans.isEmpty()) { - loge("Failing scan request because there is already an active scan"); - return false; - } - logScanRequest("addSwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings); - addPnoScanRequest(ci, handler, scanSettings, pnoSettings); - // HW PNO is not supported, we need to revert to normal background scans and - // report events after each scan and we need full scan results to get the IE information - scanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN - | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; - addBackgroundScanRequest(scanSettings); - return true; - } - - private void removeSwPnoScanRequest(ClientInfo ci, int handler) { - if (ci != null) { - Pair settings = removePnoScanRequest(ci, handler); - logScanRequest("removeSwPnoScanRequest", ci, handler, null, - settings.second, settings.first); - } - } - - private void reportPnoNetworkFound(ScanResult[] results) { - WifiScanner.ParcelableScanResults parcelableScanResults = - new WifiScanner.ParcelableScanResults(results); - for (Map.Entry, Pair> entry - : mActivePnoScans.entrySet()) { - ClientInfo ci = entry.getKey().first; - int handler = entry.getKey().second; - logCallback("pnoNetworkFound", ci, handler); - ci.reportEvent( - WifiScanner.CMD_PNO_NETWORK_FOUND, 0, handler, parcelableScanResults); - } - } - - private void sendPnoScanFailedToAllAndClear(int reason, String description) { - for (Pair key : mActivePnoScans.keySet()) { - ClientInfo ci = key.first; - int handler = key.second; - ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, - new WifiScanner.OperationResult(reason, description)); - } - mActivePnoScans.clear(); - } - - private void addBackgroundScanRequest(ScanSettings settings) { - if (DBG) localLog("Starting background scan"); - if (mInternalClientInfo != null) { - mInternalClientInfo.sendRequestToClientHandler( - WifiScanner.CMD_START_BACKGROUND_SCAN, settings, - WifiStateMachine.WIFI_WORK_SOURCE); - } - } - - private void addSingleScanRequest(ScanSettings settings) { - if (DBG) localLog("Starting single scan"); - if (mInternalClientInfo != null) { - mInternalClientInfo.sendRequestToClientHandler( - WifiScanner.CMD_START_SINGLE_SCAN, settings, - WifiStateMachine.WIFI_WORK_SOURCE); - } - } - - /** - * Checks if IE are present in scan data, if no single scan is needed to report event to - * client - */ - private boolean isSingleScanNeeded(ScanResult[] scanResults) { - for (ScanResult scanResult : scanResults) { - if (scanResult.informationElements != null - && scanResult.informationElements.length > 0) { - return false; - } - } - return true; - } - } - - private abstract class ClientInfo { - private final int mUid; - private final WorkSource mWorkSource; - private boolean mScanWorkReported = false; - protected final Messenger mMessenger; - - ClientInfo(int uid, Messenger messenger) { - mUid = uid; - mMessenger = messenger; - mWorkSource = new WorkSource(uid); - } - - /** - * Register this client to main client map. - */ - public void register() { - mClients.put(mMessenger, this); - } - - /** - * Unregister this client from main client map. - */ - private void unregister() { - mClients.remove(mMessenger); - } - - public void cleanup() { - mSingleScanStateMachine.removeSingleScanRequests(this); - mBackgroundScanStateMachine.removeBackgroundScanSettings(this); - mBackgroundScanStateMachine.removeHotlistSettings(this); - unregister(); - localLog("Successfully stopped all requests for client " + this); - } - - public int getUid() { - return mUid; - } - - public void reportEvent(int what, int arg1, int arg2) { - reportEvent(what, arg1, arg2, null); - } - - // This has to be implemented by subclasses to report events back to clients. - public abstract void reportEvent(int what, int arg1, int arg2, Object obj); - - private void reportBatchedScanStart() { - if (mUid == 0) - return; - - int csph = getCsph(); - - try { - mBatteryStats.noteWifiBatchedScanStartedFromSource(mWorkSource, csph); - } catch (RemoteException e) { - logw("failed to report scan work: " + e.toString()); - } - } - - private void reportBatchedScanStop() { - if (mUid == 0) - return; - - try { - mBatteryStats.noteWifiBatchedScanStoppedFromSource(mWorkSource); - } catch (RemoteException e) { - logw("failed to cleanup scan work: " + e.toString()); - } - } - - // TODO migrate batterystats to accept scan duration per hour instead of csph - private int getCsph() { - int totalScanDurationPerHour = 0; - Collection settingsList = - mBackgroundScanStateMachine.getBackgroundScanSettings(this); - for (ScanSettings settings : settingsList) { - int scanDurationMs = mChannelHelper.estimateScanDuration(settings); - int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / - settings.periodInMs; - totalScanDurationPerHour += scanDurationMs * scans_per_Hour; - } - - return totalScanDurationPerHour / ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS; - } - - public void reportScanWorkUpdate() { - if (mScanWorkReported) { - reportBatchedScanStop(); - mScanWorkReported = false; - } - if (mBackgroundScanStateMachine.getBackgroundScanSettings(this).isEmpty()) { - reportBatchedScanStart(); - mScanWorkReported = true; - } - } - - @Override - public String toString() { - return "ClientInfo[uid=" + mUid + "]"; - } - } - - /** - * This class is used to represent external clients to the WifiScanning Service. - */ - private class ExternalClientInfo extends ClientInfo { - private final AsyncChannel mChannel; - /** - * Indicates if the client is still connected - * If the client is no longer connected then messages to it will be silently dropped - */ - private boolean mDisconnected = false; - - ExternalClientInfo(int uid, Messenger messenger, AsyncChannel c) { - super(uid, messenger); - mChannel = c; - if (DBG) localLog("New client, channel: " + c); - } - - @Override - public void reportEvent(int what, int arg1, int arg2, Object obj) { - if (!mDisconnected) { - mChannel.sendMessage(what, arg1, arg2, obj); - } - } - - @Override - public void cleanup() { - mDisconnected = true; - // Internal clients should not have any wifi change requests. So, keeping this cleanup - // only for external client because this will otherwise cause an infinite recursion - // when the internal client in WifiChangeStateMachine is cleaned up. - mWifiChangeStateMachine.removeWifiChangeHandler(this); - mPnoScanStateMachine.removePnoSettings(this); - super.cleanup(); - } - } - - /** - * This class is used to represent internal clients to the WifiScanning Service. This is needed - * for communicating between State Machines. - * This leaves the onReportEvent method unimplemented, so that the clients have the freedom - * to handle the events as they need. - */ - private class InternalClientInfo extends ClientInfo { - private static final int INTERNAL_CLIENT_HANDLER = 0; - - /** - * The UID here is used to proxy the original external requester UID. - */ - InternalClientInfo(int requesterUid, Messenger messenger) { - super(requesterUid, messenger); - } - - @Override - public void reportEvent(int what, int arg1, int arg2, Object obj) { - Message message = Message.obtain(); - message.what = what; - message.arg1 = arg1; - message.arg2 = arg2; - message.obj = obj; - try { - mMessenger.send(message); - } catch (RemoteException e) { - loge("Failed to send message: " + what); - } - } - - /** - * Send a message to the client handler which should reroute the message to the appropriate - * state machine. - */ - public void sendRequestToClientHandler(int what, ScanSettings settings, - WorkSource workSource) { - Message msg = Message.obtain(); - msg.what = what; - msg.arg2 = INTERNAL_CLIENT_HANDLER; - if (settings != null) { - Bundle bundle = new Bundle(); - bundle.putParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY, settings); - bundle.putParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY, workSource); - msg.obj = bundle; - } - msg.replyTo = mMessenger; - msg.sendingUid = getUid(); - mClientHandler.sendMessage(msg); - } - - /** - * Send a message to the client handler which should reroute the message to the appropriate - * state machine. - */ - public void sendRequestToClientHandler(int what) { - sendRequestToClientHandler(what, null, null); - } - } - - void replySucceeded(Message msg) { - if (msg.replyTo != null) { - Message reply = Message.obtain(); - reply.what = WifiScanner.CMD_OP_SUCCEEDED; - reply.arg2 = msg.arg2; - try { - msg.replyTo.send(reply); - } catch (RemoteException e) { - // There's not much we can do if reply can't be sent! - } - } else { - // locally generated message; doesn't need a reply! - } - } - - void replyFailed(Message msg, int reason, String description) { - if (msg.replyTo != null) { - Message reply = Message.obtain(); - reply.what = WifiScanner.CMD_OP_FAILED; - reply.arg2 = msg.arg2; - reply.obj = new WifiScanner.OperationResult(reason, description); - try { - msg.replyTo.send(reply); - } catch (RemoteException e) { - // There's not much we can do if reply can't be sent! - } - } else { - // locally generated message; doesn't need a reply! - } - } - - /** - * Wifi Change state machine is used to handle any wifi change tracking requests. - * TODO: This state machine doesn't handle driver loading/unloading yet. - */ - class WifiChangeStateMachine extends StateMachine - implements WifiNative.SignificantWifiChangeEventHandler { - - private static final int MAX_APS_TO_TRACK = 3; - private static final int MOVING_SCAN_PERIOD_MS = 10000; - private static final int STATIONARY_SCAN_PERIOD_MS = 5000; - private static final int MOVING_STATE_TIMEOUT_MS = 30000; - - State mDefaultState = new DefaultState(); - State mStationaryState = new StationaryState(); - State mMovingState = new MovingState(); - - private static final String ACTION_TIMEOUT = - "com.android.server.WifiScanningServiceImpl.action.TIMEOUT"; - private PendingIntent mTimeoutIntent; - private ScanResult[] mCurrentBssids; - private InternalClientInfo mInternalClientInfo; - - private final Set> mActiveWifiChangeHandlers = new HashSet<>(); - - WifiChangeStateMachine(Looper looper) { - super("SignificantChangeStateMachine", looper); - - // CHECKSTYLE:OFF IndentationCheck - addState(mDefaultState); - addState(mStationaryState, mDefaultState); - addState(mMovingState, mDefaultState); - // CHECKSTYLE:ON IndentationCheck - - setInitialState(mDefaultState); - } - - public void removeWifiChangeHandler(ClientInfo ci) { - Iterator> iter = mActiveWifiChangeHandlers.iterator(); - while (iter.hasNext()) { - Pair entry = iter.next(); - if (entry.first == ci) { - iter.remove(); - } - } - untrackSignificantWifiChangeOnEmpty(); - } - - class DefaultState extends State { - @Override - public void enter() { - if (DBG) localLog("Entering IdleState"); - } - - @Override - public boolean processMessage(Message msg) { - if (DBG) localLog("DefaultState state got " + msg); - ClientInfo ci = mClients.get(msg.replyTo); - switch (msg.what) { - case WifiScanner.CMD_START_TRACKING_CHANGE: - addWifiChangeHandler(ci, msg.arg2); - replySucceeded(msg); - transitionTo(mMovingState); - break; - case WifiScanner.CMD_STOP_TRACKING_CHANGE: - // nothing to do - break; - case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: - /* save configuration till we transition to moving state */ - deferMessage(msg); - break; - case WifiScanner.CMD_SCAN_RESULT: - // nothing to do - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class StationaryState extends State { - @Override - public void enter() { - if (DBG) localLog("Entering StationaryState"); - reportWifiStabilized(mCurrentBssids); - } - - @Override - public boolean processMessage(Message msg) { - if (DBG) localLog("Stationary state got " + msg); - ClientInfo ci = mClients.get(msg.replyTo); - switch (msg.what) { - case WifiScanner.CMD_START_TRACKING_CHANGE: - addWifiChangeHandler(ci, msg.arg2); - replySucceeded(msg); - break; - case WifiScanner.CMD_STOP_TRACKING_CHANGE: - removeWifiChangeHandler(ci, msg.arg2); - break; - case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: - /* save configuration till we transition to moving state */ - deferMessage(msg); - break; - case CMD_WIFI_CHANGE_DETECTED: - if (DBG) localLog("Got wifi change detected"); - reportWifiChanged((ScanResult[]) msg.obj); - transitionTo(mMovingState); - break; - case WifiScanner.CMD_SCAN_RESULT: - // nothing to do - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class MovingState extends State { - boolean mWifiChangeDetected = false; - boolean mScanResultsPending = false; - - @Override - public void enter() { - if (DBG) localLog("Entering MovingState"); - if (mTimeoutIntent == null) { - Intent intent = new Intent(ACTION_TIMEOUT, null); - mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); - - mContext.registerReceiver( - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - sendMessage(CMD_WIFI_CHANGE_TIMEOUT); - } - }, new IntentFilter(ACTION_TIMEOUT)); - } - issueFullScan(); - } - - @Override - public boolean processMessage(Message msg) { - if (DBG) localLog("MovingState state got " + msg); - ClientInfo ci = mClients.get(msg.replyTo); - switch (msg.what) { - case WifiScanner.CMD_START_TRACKING_CHANGE: - addWifiChangeHandler(ci, msg.arg2); - replySucceeded(msg); - break; - case WifiScanner.CMD_STOP_TRACKING_CHANGE: - removeWifiChangeHandler(ci, msg.arg2); - break; - case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: - if (DBG) localLog("Got configuration from app"); - WifiScanner.WifiChangeSettings settings = - (WifiScanner.WifiChangeSettings) msg.obj; - reconfigureScan(settings); - mWifiChangeDetected = false; - long unchangedDelay = settings.unchangedSampleSize * settings.periodInMs; - mAlarmManager.cancel(mTimeoutIntent); - mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + unchangedDelay, - mTimeoutIntent); - break; - case WifiScanner.CMD_SCAN_RESULT: - if (DBG) localLog("Got scan results"); - if (mScanResultsPending) { - if (DBG) localLog("reconfiguring scan"); - reconfigureScan((ScanData[])msg.obj, - STATIONARY_SCAN_PERIOD_MS); - mWifiChangeDetected = false; - mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + MOVING_STATE_TIMEOUT_MS, - mTimeoutIntent); - mScanResultsPending = false; - } - break; - case CMD_WIFI_CHANGE_DETECTED: - if (DBG) localLog("Change detected"); - mAlarmManager.cancel(mTimeoutIntent); - reportWifiChanged((ScanResult[])msg.obj); - mWifiChangeDetected = true; - issueFullScan(); - break; - case CMD_WIFI_CHANGE_TIMEOUT: - if (DBG) localLog("Got timeout event"); - if (mWifiChangeDetected == false) { - transitionTo(mStationaryState); - } - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - - @Override - public void exit() { - mAlarmManager.cancel(mTimeoutIntent); - } - - void issueFullScan() { - if (DBG) localLog("Issuing full scan"); - ScanSettings settings = new ScanSettings(); - settings.band = WifiScanner.WIFI_BAND_BOTH; - settings.periodInMs = MOVING_SCAN_PERIOD_MS; - settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; - addScanRequest(settings); - mScanResultsPending = true; - } - - } - - private void reconfigureScan(ScanData[] results, int period) { - // find brightest APs and set them as sentinels - if (results.length < MAX_APS_TO_TRACK) { - localLog("too few APs (" + results.length + ") available to track wifi change"); - return; - } - - removeScanRequest(); - - // remove duplicate BSSIDs - HashMap bssidToScanResult = new HashMap(); - for (ScanResult result : results[0].getResults()) { - ScanResult saved = bssidToScanResult.get(result.BSSID); - if (saved == null) { - bssidToScanResult.put(result.BSSID, result); - } else if (saved.level > result.level) { - bssidToScanResult.put(result.BSSID, result); - } - } - - // find brightest BSSIDs - ScanResult brightest[] = new ScanResult[MAX_APS_TO_TRACK]; - Collection results2 = bssidToScanResult.values(); - for (ScanResult result : results2) { - for (int j = 0; j < brightest.length; j++) { - if (brightest[j] == null - || (brightest[j].level < result.level)) { - for (int k = brightest.length; k > (j + 1); k--) { - brightest[k - 1] = brightest[k - 2]; - } - brightest[j] = result; - break; - } - } - } - - // Get channels to scan for - ArrayList channels = new ArrayList(); - for (int i = 0; i < brightest.length; i++) { - boolean found = false; - for (int j = i + 1; j < brightest.length; j++) { - if (brightest[j].frequency == brightest[i].frequency) { - found = true; - } - } - if (!found) { - channels.add(brightest[i].frequency); - } - } - - if (DBG) localLog("Found " + channels.size() + " channels"); - - // set scanning schedule - ScanSettings settings = new ScanSettings(); - settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; - settings.channels = new ChannelSpec[channels.size()]; - for (int i = 0; i < channels.size(); i++) { - settings.channels[i] = new ChannelSpec(channels.get(i)); - } - - settings.periodInMs = period; - addScanRequest(settings); - - WifiScanner.WifiChangeSettings settings2 = new WifiScanner.WifiChangeSettings(); - settings2.rssiSampleSize = 3; - settings2.lostApSampleSize = 3; - settings2.unchangedSampleSize = 3; - settings2.minApsBreachingThreshold = 2; - settings2.bssidInfos = new BssidInfo[brightest.length]; - - for (int i = 0; i < brightest.length; i++) { - BssidInfo BssidInfo = new BssidInfo(); - BssidInfo.bssid = brightest[i].BSSID; - int threshold = (100 + brightest[i].level) / 32 + 2; - BssidInfo.low = brightest[i].level - threshold; - BssidInfo.high = brightest[i].level + threshold; - settings2.bssidInfos[i] = BssidInfo; - - if (DBG) localLog("Setting bssid=" + BssidInfo.bssid + ", " + - "low=" + BssidInfo.low + ", high=" + BssidInfo.high); - } - - trackSignificantWifiChange(settings2); - mCurrentBssids = brightest; - } - - private void reconfigureScan(WifiScanner.WifiChangeSettings settings) { - - if (settings.bssidInfos.length < MAX_APS_TO_TRACK) { - localLog("too few APs (" + settings.bssidInfos.length - + ") available to track wifi change"); - return; - } - - if (DBG) localLog("Setting configuration specified by app"); - - mCurrentBssids = new ScanResult[settings.bssidInfos.length]; - HashSet channels = new HashSet(); - - for (int i = 0; i < settings.bssidInfos.length; i++) { - ScanResult result = new ScanResult(); - result.BSSID = settings.bssidInfos[i].bssid; - mCurrentBssids[i] = result; - channels.add(settings.bssidInfos[i].frequencyHint); - } - - // cancel previous scan - removeScanRequest(); - - // set new scanning schedule - ScanSettings settings2 = new ScanSettings(); - settings2.band = WifiScanner.WIFI_BAND_UNSPECIFIED; - settings2.channels = new ChannelSpec[channels.size()]; - int i = 0; - for (Integer channel : channels) { - settings2.channels[i++] = new ChannelSpec(channel); - } - - settings2.periodInMs = settings.periodInMs; - addScanRequest(settings2); - - // start tracking new APs - trackSignificantWifiChange(settings); - } - - - @Override - public void onChangesFound(ScanResult results[]) { - sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results); - } - - private void addScanRequest(ScanSettings settings) { - if (DBG) localLog("Starting scans"); - if (mInternalClientInfo != null) { - mInternalClientInfo.sendRequestToClientHandler( - WifiScanner.CMD_START_BACKGROUND_SCAN, settings, null); - } - } - - private void removeScanRequest() { - if (DBG) localLog("Stopping scans"); - if (mInternalClientInfo != null) { - mInternalClientInfo.sendRequestToClientHandler( - WifiScanner.CMD_STOP_BACKGROUND_SCAN); - } - } - - private void trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings) { - mScannerImpl.untrackSignificantWifiChange(); - mScannerImpl.trackSignificantWifiChange(settings, this); - } - - private void untrackSignificantWifiChange() { - mScannerImpl.untrackSignificantWifiChange(); - } - - private void addWifiChangeHandler(ClientInfo ci, int handler) { - mActiveWifiChangeHandlers.add(Pair.create(ci, handler)); - // Add an internal client to make background scan requests. - if (mInternalClientInfo == null) { - mInternalClientInfo = - new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler())); - mInternalClientInfo.register(); - } - } - - private void removeWifiChangeHandler(ClientInfo ci, int handler) { - mActiveWifiChangeHandlers.remove(Pair.create(ci, handler)); - untrackSignificantWifiChangeOnEmpty(); - } - - private void untrackSignificantWifiChangeOnEmpty() { - if (mActiveWifiChangeHandlers.isEmpty()) { - if (DBG) localLog("Got Disable Wifi Change"); - mCurrentBssids = null; - untrackSignificantWifiChange(); - // Remove the internal client when there are no more external clients. - if (mInternalClientInfo != null) { - mInternalClientInfo.cleanup(); - mInternalClientInfo = null; - } - transitionTo(mDefaultState); - } - } - - private void reportWifiChanged(ScanResult[] results) { - WifiScanner.ParcelableScanResults parcelableScanResults = - new WifiScanner.ParcelableScanResults(results); - Iterator> it = mActiveWifiChangeHandlers.iterator(); - while (it.hasNext()) { - Pair entry = it.next(); - ClientInfo ci = entry.first; - int handler = entry.second; - ci.reportEvent(WifiScanner.CMD_WIFI_CHANGE_DETECTED, 0, handler, - parcelableScanResults); - } - } - - private void reportWifiStabilized(ScanResult[] results) { - WifiScanner.ParcelableScanResults parcelableScanResults = - new WifiScanner.ParcelableScanResults(results); - Iterator> it = mActiveWifiChangeHandlers.iterator(); - while (it.hasNext()) { - Pair entry = it.next(); - ClientInfo ci = entry.first; - int handler = entry.second; - ci.reportEvent(WifiScanner.CMD_WIFI_CHANGES_STABILIZED, 0, handler, - parcelableScanResults); - } - } - } - - private static String toString(int uid, ScanSettings settings) { - StringBuilder sb = new StringBuilder(); - sb.append("ScanSettings[uid=").append(uid); - sb.append(", period=").append(settings.periodInMs); - sb.append(", report=").append(settings.reportEvents); - if (settings.reportEvents == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL - && settings.numBssidsPerScan > 0 - && settings.maxScansToCache > 1) { - sb.append(", batch=").append(settings.maxScansToCache); - sb.append(", numAP=").append(settings.numBssidsPerScan); - } - sb.append(", ").append(ChannelHelper.toString(settings)); - sb.append("]"); - - return sb.toString(); - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump WifiScanner from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() - + " without permission " - + android.Manifest.permission.DUMP); - return; - } - pw.println("WifiScanningService - Log Begin ----"); - mLocalLog.dump(fd, pw, args); - pw.println("WifiScanningService - Log End ----"); - pw.println(); - pw.println("clients:"); - for (ClientInfo client : mClients.values()) { - pw.println(" " + client); - } - pw.println("listeners:"); - for (ClientInfo client : mClients.values()) { - Collection settingsList = - mBackgroundScanStateMachine.getBackgroundScanSettings(client); - for (ScanSettings settings : settingsList) { - pw.println(" " + toString(client.mUid, settings)); - } - } - WifiNative.ScanSettings schedule = mScheduler.getSchedule(); - if (schedule != null) { - pw.println("schedule:"); - pw.println(" base period: " + schedule.base_period_ms); - pw.println(" max ap per scan: " + schedule.max_ap_per_scan); - pw.println(" batched scans: " + schedule.report_threshold_num_scans); - pw.println(" buckets:"); - for (int b = 0; b < schedule.num_buckets; b++) { - WifiNative.BucketSettings bucket = schedule.buckets[b]; - pw.println(" bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)[" - + bucket.report_events + "]: " - + ChannelHelper.toString(bucket)); - } - } - pw.println("PNO scan state machine transitions:"); - mPnoScanStateMachine.dump(fd, pw, args); - } - - void logScanRequest(String request, ClientInfo ci, int id, WorkSource workSource, - ScanSettings settings, PnoSettings pnoSettings) { - StringBuilder sb = new StringBuilder(); - sb.append(request) - .append(": ") - .append(ci.toString()) - .append(",Id=") - .append(id); - if (workSource != null) { - sb.append(",").append(workSource); - } - if (settings != null) { - sb.append(", "); - describeTo(sb, settings); - } - if (pnoSettings != null) { - sb.append(", "); - describeTo(sb, pnoSettings); - } - localLog(sb.toString()); - } - - void logCallback(String callback, ClientInfo ci, int id) { - StringBuilder sb = new StringBuilder(); - sb.append(callback) - .append(": ") - .append(ci.toString()) - .append(",Id=") - .append(id); - localLog(sb.toString()); - } - - static String describeTo(StringBuilder sb, ScanSettings scanSettings) { - sb.append("ScanSettings { ") - .append(" band:").append(scanSettings.band) - .append(" period:").append(scanSettings.periodInMs) - .append(" reportEvents:").append(scanSettings.reportEvents) - .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan) - .append(" maxScansToCache:").append(scanSettings.maxScansToCache) - .append(" channels:[ "); - if (scanSettings.channels != null) { - for (int i = 0; i < scanSettings.channels.length; i++) { - sb.append(scanSettings.channels[i].frequency) - .append(" "); - } - } - sb.append(" ] ") - .append(" } "); - return sb.toString(); - } - - static String describeTo(StringBuilder sb, PnoSettings pnoSettings) { - sb.append("PnoSettings { ") - .append(" min5GhzRssi:").append(pnoSettings.min5GHzRssi) - .append(" min24GhzRssi:").append(pnoSettings.min24GHzRssi) - .append(" initialScoreMax:").append(pnoSettings.initialScoreMax) - .append(" currentConnectionBonus:").append(pnoSettings.currentConnectionBonus) - .append(" sameNetworkBonus:").append(pnoSettings.sameNetworkBonus) - .append(" secureBonus:").append(pnoSettings.secureBonus) - .append(" band5GhzBonus:").append(pnoSettings.band5GHzBonus) - .append(" isConnected:").append(pnoSettings.isConnected) - .append(" networks:[ "); - if (pnoSettings.networkList != null) { - for (int i = 0; i < pnoSettings.networkList.length; i++) { - sb.append(pnoSettings.networkList[i].ssid) - .append(",") - .append(pnoSettings.networkList[i].networkId) - .append(" "); - } - } - sb.append(" ] ") - .append(" } "); - return sb.toString(); - } -} diff --git a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java new file mode 100644 index 000000000..18f159f7f --- /dev/null +++ b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java @@ -0,0 +1,177 @@ +/* + * 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.content.Context; +import android.net.wifi.WifiScanner; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.android.server.wifi.WifiNative; + +/** + * WifiScanner implementation that takes advantage of the gscan HAL API + * The gscan API is used to perform background scans and wpa_supplicant is used for onehot scans. + * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method. + */ +public class HalWifiScannerImpl extends WifiScannerImpl implements Handler.Callback { + private static final String TAG = "HalWifiScannerImpl"; + private static final boolean DBG = false; + + private final WifiNative mWifiNative; + private final ChannelHelper mChannelHelper; + private final SupplicantWifiScannerImpl mSupplicantScannerDelegate; + private final boolean mHalBasedPnoSupported; + + public HalWifiScannerImpl(Context context, WifiNative wifiNative, Looper looper) { + mWifiNative = wifiNative; + mChannelHelper = new HalChannelHelper(wifiNative); + mSupplicantScannerDelegate = + new SupplicantWifiScannerImpl(context, wifiNative, mChannelHelper, looper); + + // Check if ePNO is supported by the HAL. + int halFeatureSet = mWifiNative.getSupportedFeatureSet(); + mHalBasedPnoSupported = false; + /* TODO(b/27877781): Swith ePNO on + mHalBasedPnoSupported = + ((halFeatureSet & WifiManager.WIFI_FEATURE_HAL_EPNO) + == WifiManager.WIFI_FEATURE_HAL_EPNO); */ + } + + @Override + public boolean handleMessage(Message msg) { + Log.w(TAG, "Unknown message received: " + msg.what); + return true; + } + + @Override + public void cleanup() { + mSupplicantScannerDelegate.cleanup(); + } + + @Override + public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) { + return mWifiNative.getScanCapabilities(capabilities); + } + + @Override + public ChannelHelper getChannelHelper() { + return mChannelHelper; + } + + public boolean startSingleScan(WifiNative.ScanSettings settings, + WifiNative.ScanEventHandler eventHandler) { + return mSupplicantScannerDelegate.startSingleScan(settings, eventHandler); + } + + @Override + public WifiScanner.ScanData getLatestSingleScanResults() { + return mSupplicantScannerDelegate.getLatestSingleScanResults(); + } + + @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; + } + return mWifiNative.startScan(settings, eventHandler); + } + + @Override + public void stopBatchedScan() { + mWifiNative.stopScan(); + } + + @Override + public void pauseBatchedScan() { + mWifiNative.pauseScan(); + } + + @Override + public void restartBatchedScan() { + mWifiNative.restartScan(); + } + + @Override + public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) { + return mWifiNative.getScanResults(flush); + } + + @Override + public boolean setHwPnoList(WifiNative.PnoSettings settings, + WifiNative.PnoEventHandler eventHandler) { + if (mHalBasedPnoSupported) { + return mWifiNative.setPnoList(settings, eventHandler); + } else { + return mSupplicantScannerDelegate.setHwPnoList(settings, eventHandler); + } + } + + @Override + public boolean resetHwPnoList() { + if (mHalBasedPnoSupported) { + return mWifiNative.resetPnoList(); + } else { + return mSupplicantScannerDelegate.resetHwPnoList(); + } + } + + @Override + public boolean isHwPnoSupported(boolean isConnectedPno) { + if (mHalBasedPnoSupported) { + return true; + } else { + return mSupplicantScannerDelegate.isHwPnoSupported(isConnectedPno); + } + } + + @Override + public boolean shouldScheduleBackgroundScanForHwPno() { + if (mHalBasedPnoSupported) { + return true; + } else { + return mSupplicantScannerDelegate.shouldScheduleBackgroundScanForHwPno(); + } + } + + @Override + public boolean setHotlist(WifiScanner.HotlistSettings settings, + WifiNative.HotlistEventHandler eventHandler) { + return mWifiNative.setHotlist(settings, eventHandler); + } + + @Override + public void resetHotlist() { + mWifiNative.resetHotlist(); + } + + @Override + public boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings, + WifiNative.SignificantWifiChangeEventHandler handler) { + return mWifiNative.trackSignificantWifiChange(settings, handler); + } + + @Override + public void untrackSignificantWifiChange() { + mWifiNative.untrackSignificantWifiChange(); + } +} diff --git a/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java new file mode 100644 index 000000000..0ef36f565 --- /dev/null +++ b/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java @@ -0,0 +1,1118 @@ +/* + * 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.os.SystemClock; +import android.util.Log; + +import com.android.internal.R; +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"; + + 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 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 = 10000; + + 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) { + mContext = context; + mWifiNative = wifiNative; + mChannelHelper = channelHelper; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mEventHandler = new Handler(looper, this); + mHwPnoDebouncer = new HwPnoDebouncer(mWifiNative, mAlarmManager, mEventHandler); + + // 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) { + // TODO figure out how to get channel information from supplicant + this(context, wifiNative, new NoBandChannelHelper(), looper); + } + + @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(); + 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(SystemClock.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) { + if (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) { + for (int i = 0; i < hiddenNetworkIds.length; i++) { + hiddenNetworkIdSet.add(hiddenNetworkIds[i]); + } + } + } + + mNextBackgroundScanPeriod++; + mBackgroundScanPeriodPending = false; + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + 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) { + for (int i = 0; i < hiddenNetworkIds.length; 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 + Log.d(TAG, "Starting wifi scan for freqs=" + freqs + + ", background=" + newScanSettings.backgroundScanActive + + ", single=" + newScanSettings.singleScanActive); + mLastScanSettings = newScanSettings; + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + 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()) { + Log.d(TAG, "Starting wifi PNO scan"); + 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 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) { + mWifiNative = wifiNative; + mAlarmManager = alarmManager; + mEventHandler = eventHandler; + } + + /** + * 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; + } + Log.d(TAG, "Change PNO state from " + mCurrentPnoState + " to " + enable); + + mLastPnoChangeTimeStamp = System.currentTimeMillis(); + if (mWifiNative.setPnoScan(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 = System.currentTimeMillis() - 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.RTC_WAKEUP, + System.currentTimeMillis() + 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); + } + } + } +} diff --git a/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java new file mode 100644 index 000000000..66c3eda6f --- /dev/null +++ b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java @@ -0,0 +1,178 @@ +/* + * 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.content.Context; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiScanner; +import android.os.Looper; + +import com.android.server.wifi.WifiNative; + +import java.util.Comparator; + +/** + * Defines the interface to the Wifi hardware required for the WifiScanner API + */ +public abstract class WifiScannerImpl { + + /** + * A factory that create a {@link com.android.server.wifi.scanner.WifiScannerImpl} + */ + public static interface WifiScannerImplFactory { + WifiScannerImpl create(Context context, Looper looper); + } + + /** + * Factory that create the implementation that is most appropriate for the system. + * This factory should only ever be used once. + */ + public static final WifiScannerImplFactory DEFAULT_FACTORY = new WifiScannerImplFactory() { + public WifiScannerImpl create(Context context, Looper looper) { + WifiNative wifiNative = WifiNative.getWlanNativeInterface(); + if (wifiNative.getScanCapabilities(new WifiNative.ScanCapabilities())) { + return new HalWifiScannerImpl(context, wifiNative, looper); + } else { + return new SupplicantWifiScannerImpl(context, wifiNative, looper); + } + } + }; + + /** + * A comparator that implements the sort order that is expected for scan results + */ + protected static final Comparator SCAN_RESULT_SORT_COMPARATOR = + new Comparator() { + public int compare(ScanResult r1, ScanResult r2) { + return r2.level - r1.level; + } + }; + + /** + * Cleanup any ongoing operations. This may be called when the driver is unloaded. + * There is no expectation that failure events are returned for ongoing operations. + */ + public abstract void cleanup(); + + /** + * Get the supported scan capabilities. + * + * @param capabilities Object that will be filled with the supported capabilities if successful + * @return true if the scan capabilities were retrieved successfully + */ + public abstract boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities); + + /** + * Get a ChannelHelper that can be used to perform operations on scan channels + */ + public abstract ChannelHelper getChannelHelper(); + + /** + * Start a one time scan. This method should only be called when there is no scan going on + * (after a callback indicating that the previous scan succeeded/failed). + * @return if the scan paramaters are valid + * Note this may return true even if the parameters are not accepted by the chip because the + * scan may be scheduled async. + */ + public abstract boolean startSingleScan(WifiNative.ScanSettings settings, + WifiNative.ScanEventHandler eventHandler); + /** + * Get the scan results of the most recent single scan. This should be called immediately when + * the scan success callback is receieved. + */ + public abstract WifiScanner.ScanData getLatestSingleScanResults(); + + /** + * Start a background scan. Calling this method while a background scan is already in process + * will interrupt the previous scan settings and replace it with the new ones. + * @return if the scan paramaters are valid + * Note this may return true even if the parameters are not accepted by the chip because the + * scan may be scheduled async. + */ + public abstract boolean startBatchedScan(WifiNative.ScanSettings settings, + WifiNative.ScanEventHandler eventHandler); + /** + * Stop the currently active background scan + */ + public abstract void stopBatchedScan(); + + /** + * Pause the currently active background scan + */ + public abstract void pauseBatchedScan(); + + /** + * Restart the currently paused background scan + */ + public abstract void restartBatchedScan(); + + /** + * Get the latest cached scan results from the last scan event. This should be called + * immediately when the scan success callback is receieved. + */ + public abstract WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush); + + /** + * Set PNO list to start PNO background scan. + * @param settings PNO settings for this scan. + * @param eventHandler Event handler for notifying the scan results. + * @return true if success, false otherwise + */ + public abstract boolean setHwPnoList(WifiNative.PnoSettings settings, + WifiNative.PnoEventHandler eventHandler); + + /** + * Reset PNO list to terminate PNO background scan. + * @return true if success, false otherwise + */ + public abstract boolean resetHwPnoList(); + + /** + * This returns whether HW PNO is supported or not. + * @param isConnectedPno Whether this is connected PNO vs disconnected PNO. + * @return true if HW PNO is supported, false otherwise. + */ + public abstract boolean isHwPnoSupported(boolean isConnectedPno); + + /** + * This returns whether a background scan should be running for HW PNO scan or not. + * @return true if background scan needs to be started, false otherwise. + */ + public abstract boolean shouldScheduleBackgroundScanForHwPno(); + + /** + * Set a new hotlist + */ + public abstract boolean setHotlist(WifiScanner.HotlistSettings settings, + WifiNative.HotlistEventHandler eventHandler); + + /** + * Reset any active hotlist + */ + public abstract void resetHotlist(); + + /** + * Start tracking significant wifi changes + */ + public abstract boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings, + WifiNative.SignificantWifiChangeEventHandler handler); + + /** + * Stop tracking significant wifi changes + */ + public abstract void untrackSignificantWifiChange(); +} diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningService.java b/service/java/com/android/server/wifi/scanner/WifiScanningService.java new file mode 100644 index 000000000..93ac72252 --- /dev/null +++ b/service/java/com/android/server/wifi/scanner/WifiScanningService.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008 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.content.Context; +import android.os.HandlerThread; +import android.util.Log; + +import com.android.server.SystemService; +import com.android.server.am.BatteryStatsService; +import com.android.server.wifi.WifiInjector; + +public class WifiScanningService extends SystemService { + + static final String TAG = "WifiScanningService"; + private final WifiScanningServiceImpl mImpl; + private final HandlerThread mHandlerThread; + + public WifiScanningService(Context context) { + super(context); + Log.i(TAG, "Creating " + Context.WIFI_SCANNING_SERVICE); + mHandlerThread = new HandlerThread("WifiScanningService"); + mHandlerThread.start(); + mImpl = new WifiScanningServiceImpl(getContext(), mHandlerThread.getLooper(), + WifiScannerImpl.DEFAULT_FACTORY, BatteryStatsService.getService(), + WifiInjector.getInstance()); + } + + @Override + public void onStart() { + Log.i(TAG, "Publishing " + Context.WIFI_SCANNING_SERVICE); + publishBinderService(Context.WIFI_SCANNING_SERVICE, mImpl); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + Log.i(TAG, "Starting " + Context.WIFI_SCANNING_SERVICE); + mImpl.startService(); + } + } +} diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java new file mode 100644 index 000000000..524b76dd7 --- /dev/null +++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java @@ -0,0 +1,2601 @@ +/* + * Copyright (C) 2008 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.Manifest; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.wifi.IWifiScanner; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiScanner; +import android.net.wifi.WifiScanner.BssidInfo; +import android.net.wifi.WifiScanner.ChannelSpec; +import android.net.wifi.WifiScanner.PnoSettings; +import android.net.wifi.WifiScanner.ScanData; +import android.net.wifi.WifiScanner.ScanSettings; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.os.WorkSource; +import android.util.ArrayMap; +import android.util.LocalLog; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.server.wifi.WifiInjector; +import com.android.server.wifi.WifiMetrics; +import com.android.server.wifi.WifiMetricsProto; +import com.android.server.wifi.WifiNative; +import com.android.server.wifi.WifiStateMachine; +import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class WifiScanningServiceImpl extends IWifiScanner.Stub { + + private static final String TAG = WifiScanningService.TAG; + private static final boolean DBG = false; + + private static final int MIN_PERIOD_PER_CHANNEL_MS = 200; // DFS needs 120 ms + private static final int UNKNOWN_PID = -1; + + private static final LocalLog mLocalLog = new LocalLog(1024); + + private final WifiMetrics mWifiMetrics; + + private static void localLog(String message) { + mLocalLog.log(message); + } + + private static void logw(String message) { + Log.w(TAG, message); + mLocalLog.log(message); + } + + private static void loge(String message) { + Log.e(TAG, message); + mLocalLog.log(message); + } + + private WifiScannerImpl mScannerImpl; + + @Override + public Messenger getMessenger() { + if (mClientHandler != null) { + return new Messenger(mClientHandler); + } else { + loge("WifiScanningServiceImpl trying to get messenger w/o initialization"); + return null; + } + } + + @Override + public Bundle getAvailableChannels(int band) { + mChannelHelper.updateChannels(); + ChannelSpec[] channelSpecs = mChannelHelper.getAvailableScanChannels(band); + ArrayList list = new ArrayList(channelSpecs.length); + for (ChannelSpec channelSpec : channelSpecs) { + list.add(channelSpec.frequency); + } + Bundle b = new Bundle(); + b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list); + return b; + } + + private void enforceLocationHardwarePermission(int uid) { + mContext.enforcePermission( + Manifest.permission.LOCATION_HARDWARE, + UNKNOWN_PID, uid, + "LocationHardware"); + } + + private class ClientHandler extends Handler { + + ClientHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { + ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); + if (client != null) { + logw("duplicate client connection: " + msg.sendingUid); + client.mChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, + AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED); + return; + } + + AsyncChannel ac = new AsyncChannel(); + ac.connected(mContext, this, msg.replyTo); + + client = new ExternalClientInfo(msg.sendingUid, msg.replyTo, ac); + client.register(); + + ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, + AsyncChannel.STATUS_SUCCESSFUL); + + if (DBG) Log.d(TAG, "client connected: " + client); + return; + } + case AsyncChannel.CMD_CHANNEL_DISCONNECT: { + ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); + if (client != null) { + client.mChannel.disconnect(); + } + return; + } + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { + ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo); + if (client != null) { + if (DBG) { + Log.d(TAG, "client disconnected: " + client + ", reason: " + msg.arg1); + } + client.cleanup(); + } + return; + } + } + + try { + enforceLocationHardwarePermission(msg.sendingUid); + } catch (SecurityException e) { + localLog("failed to authorize app: " + e); + replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized"); + return; + } + + // Since this message is sent from WifiScanner using |sendMessageSynchronously| which + // doesn't set the correct |msg.replyTo| field. + if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) { + mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); + return; + } + + ClientInfo ci = mClients.get(msg.replyTo); + if (ci == null) { + loge("Could not find client info for message " + msg.replyTo); + replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener"); + return; + } + + switch (msg.what) { + case WifiScanner.CMD_START_BACKGROUND_SCAN: + case WifiScanner.CMD_STOP_BACKGROUND_SCAN: + case WifiScanner.CMD_SET_HOTLIST: + case WifiScanner.CMD_RESET_HOTLIST: + mBackgroundScanStateMachine.sendMessage(Message.obtain(msg)); + break; + case WifiScanner.CMD_START_PNO_SCAN: + case WifiScanner.CMD_STOP_PNO_SCAN: + mPnoScanStateMachine.sendMessage(Message.obtain(msg)); + break; + case WifiScanner.CMD_START_SINGLE_SCAN: + case WifiScanner.CMD_STOP_SINGLE_SCAN: + mSingleScanStateMachine.sendMessage(Message.obtain(msg)); + break; + case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: + case WifiScanner.CMD_START_TRACKING_CHANGE: + case WifiScanner.CMD_STOP_TRACKING_CHANGE: + mWifiChangeStateMachine.sendMessage(Message.obtain(msg)); + break; + default: + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "Invalid request"); + break; + } + } + } + + private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE; + + private static final int CMD_SCAN_RESULTS_AVAILABLE = BASE + 0; + private static final int CMD_FULL_SCAN_RESULTS = BASE + 1; + private static final int CMD_HOTLIST_AP_FOUND = BASE + 2; + private static final int CMD_HOTLIST_AP_LOST = BASE + 3; + private static final int CMD_WIFI_CHANGE_DETECTED = BASE + 4; + private static final int CMD_WIFI_CHANGE_TIMEOUT = BASE + 5; + private static final int CMD_DRIVER_LOADED = BASE + 6; + private static final int CMD_DRIVER_UNLOADED = BASE + 7; + private static final int CMD_SCAN_PAUSED = BASE + 8; + private static final int CMD_SCAN_RESTARTED = BASE + 9; + private static final int CMD_SCAN_FAILED = BASE + 10; + private static final int CMD_PNO_NETWORK_FOUND = BASE + 11; + private static final int CMD_PNO_SCAN_FAILED = BASE + 12; + + private final Context mContext; + private final Looper mLooper; + private final WifiScannerImpl.WifiScannerImplFactory mScannerImplFactory; + private final ArrayMap mClients; + + private ChannelHelper mChannelHelper; + private BackgroundScanScheduler mScheduler; + private WifiNative.ScanSettings mPreviousSchedule; + + private WifiBackgroundScanStateMachine mBackgroundScanStateMachine; + private WifiSingleScanStateMachine mSingleScanStateMachine; + private WifiChangeStateMachine mWifiChangeStateMachine; + private WifiPnoScanStateMachine mPnoScanStateMachine; + private ClientHandler mClientHandler; + private final IBatteryStats mBatteryStats; + private final AlarmManager mAlarmManager; + + WifiScanningServiceImpl(Context context, Looper looper, + WifiScannerImpl.WifiScannerImplFactory scannerImplFactory, IBatteryStats batteryStats, + WifiInjector wifiInjector) { + mContext = context; + mLooper = looper; + mScannerImplFactory = scannerImplFactory; + mBatteryStats = batteryStats; + mClients = new ArrayMap<>(); + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mWifiMetrics = wifiInjector.getWifiMetrics(); + + mPreviousSchedule = null; + } + + public void startService() { + mClientHandler = new ClientHandler(mLooper); + mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper); + mWifiChangeStateMachine = new WifiChangeStateMachine(mLooper); + mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper); + mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper); + + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra( + WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED); + if (DBG) localLog("SCAN_AVAILABLE : " + state); + if (state == WifiManager.WIFI_STATE_ENABLED) { + mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_LOADED); + mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED); + mPnoScanStateMachine.sendMessage(CMD_DRIVER_LOADED); + } else if (state == WifiManager.WIFI_STATE_DISABLED) { + mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); + mSingleScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); + mPnoScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); + } + } + }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE)); + + mBackgroundScanStateMachine.start(); + mWifiChangeStateMachine.start(); + mSingleScanStateMachine.start(); + mPnoScanStateMachine.start(); + } + + /** + * A map of objects of a WifiScanner client and handler id to an object of type T + */ + private static class ClientHandlerMap extends HashMap, T> { + public T put(ClientInfo ci, int handler, T value) { + return put(Pair.create(ci, handler), value); + } + + public T get(ClientInfo ci, int handler) { + return get(Pair.create(ci, handler)); + } + + public T remove(ClientInfo ci, int handler) { + return remove(Pair.create(ci, handler)); + } + + public Collection getAllValues(ClientInfo ci) { + ArrayList list = new ArrayList<>(); + for (Map.Entry, T> entry : entrySet()) { + if (entry.getKey().first == ci) { + list.add(entry.getValue()); + } + } + return list; + } + + public void removeAll(ClientInfo ci) { + Iterator, T>> iter = entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry, T> entry = iter.next(); + if (entry.getKey().first == ci) { + iter.remove(); + } + } + } + + public Map getHandlerToValueMap(ClientInfo ci) { + Map handlerToValueMap = new HashMap<>(); + for (Map.Entry, T> entry : entrySet()) { + if (entry.getKey().first == ci) { + handlerToValueMap.put(entry.getKey().second, entry.getValue()); + } + } + return handlerToValueMap; + } + } + + private static boolean isWorkSourceValid(WorkSource workSource) { + return workSource != null && workSource.size() > 0 && workSource.get(0) >= 0; + } + + private static WorkSource computeWorkSource(ClientInfo ci, WorkSource requestedWorkSource) { + if (requestedWorkSource != null) { + if (isWorkSourceValid(requestedWorkSource)) { + // Wifi currently doesn't use names, so need to clear names out of the + // supplied WorkSource to allow future WorkSource combining. + requestedWorkSource.clearNames(); + return requestedWorkSource; + } else { + loge("Got invalid work source request: " + requestedWorkSource.toString() + + " from " + ci); + } + } + WorkSource callingWorkSource = new WorkSource(ci.getUid()); + if (isWorkSourceValid(callingWorkSource)) { + return callingWorkSource; + } else { + loge("Client has invalid work source: " + callingWorkSource); + return new WorkSource(); + } + } + + private static class RequestInfo { + final ClientInfo clientInfo; + final int handlerId; + final WorkSource workSource; + final T settings; + + RequestInfo(ClientInfo clientInfo, int handlerId, WorkSource requestedWorkSource, + T settings) { + this.clientInfo = clientInfo; + this.handlerId = handlerId; + this.settings = settings; + this.workSource = computeWorkSource(clientInfo, requestedWorkSource); + } + + void reportEvent(int what, int arg1, Object obj) { + clientInfo.reportEvent(what, arg1, handlerId, obj); + } + } + + private static class RequestList extends ArrayList> { + void removeAllForHandler(ClientInfo ci, int handlerId) { + Iterator> iter = iterator(); + while (iter.hasNext()) { + RequestInfo entry = iter.next(); + if (entry.clientInfo == ci && entry.handlerId == handlerId) { + iter.remove(); + } + } + } + + void removeAllForClient(ClientInfo ci) { + Iterator> iter = iterator(); + while (iter.hasNext()) { + RequestInfo entry = iter.next(); + if (entry.clientInfo == ci) { + iter.remove(); + } + } + } + + WorkSource createMergedWorkSource() { + WorkSource mergedSource = new WorkSource(); + for (RequestInfo entry : this) { + mergedSource.add(entry.workSource); + } + return mergedSource; + } + } + + /** + * State machine that holds the state of single scans. Scans should only be active in the + * ScanningState. The pending scans and active scans maps are swaped when entering + * ScanningState. Any requests queued while scanning will be placed in the pending queue and + * executed after transitioning back to IdleState. + */ + class WifiSingleScanStateMachine extends StateMachine implements WifiNative.ScanEventHandler { + private final DefaultState mDefaultState = new DefaultState(); + private final DriverStartedState mDriverStartedState = new DriverStartedState(); + private final IdleState mIdleState = new IdleState(); + private final ScanningState mScanningState = new ScanningState(); + + private RequestList mActiveScans = new RequestList<>(); + private RequestList mPendingScans = new RequestList<>(); + + WifiSingleScanStateMachine(Looper looper) { + super("WifiSingleScanStateMachine", looper); + + setLogRecSize(128); + setLogOnlyTransitions(false); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mDriverStartedState, mDefaultState); + addState(mIdleState, mDriverStartedState); + addState(mScanningState, mDriverStartedState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mDefaultState); + } + + /** + * Called to indicate a change in state for the current scan. + * Will dispatch a coresponding event to the state machine + */ + @Override + public void onScanStatus(int event) { + if (DBG) localLog("onScanStatus event received, event=" + event); + switch(event) { + case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: + case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: + case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: + sendMessage(CMD_SCAN_RESULTS_AVAILABLE); + break; + case WifiNative.WIFI_SCAN_FAILED: + sendMessage(CMD_SCAN_FAILED); + break; + default: + Log.e(TAG, "Unknown scan status event: " + event); + break; + } + } + + /** + * Called for each full scan result if requested + */ + @Override + public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { + if (DBG) localLog("onFullScanResult received"); + sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); + } + + @Override + public void onScanPaused(ScanData[] scanData) { + // should not happen for single scan + Log.e(TAG, "Got scan paused for single scan"); + } + + @Override + public void onScanRestarted() { + // should not happen for single scan + Log.e(TAG, "Got scan restarted for single scan"); + } + + class DefaultState extends State { + @Override + public void enter() { + mActiveScans.clear(); + mPendingScans.clear(); + } + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_DRIVER_LOADED: + transitionTo(mIdleState); + return HANDLED; + case CMD_DRIVER_UNLOADED: + transitionTo(mDefaultState); + return HANDLED; + case WifiScanner.CMD_START_SINGLE_SCAN: + case WifiScanner.CMD_STOP_SINGLE_SCAN: + replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); + return HANDLED; + case CMD_SCAN_RESULTS_AVAILABLE: + if (DBG) localLog("ignored scan results available event"); + return HANDLED; + case CMD_FULL_SCAN_RESULTS: + if (DBG) localLog("ignored full scan result event"); + return HANDLED; + default: + return NOT_HANDLED; + } + + } + } + + /** + * State representing when the driver is running. This state is not meant to be transitioned + * directly, but is instead indented as a parent state of ScanningState and IdleState + * to hold common functionality and handle cleaning up scans when the driver is shut down. + */ + class DriverStartedState extends State { + @Override + public void exit() { + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED, + mPendingScans.size()); + sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, + "Scan was interrupted"); + } + + @Override + public boolean processMessage(Message msg) { + ClientInfo ci = mClients.get(msg.replyTo); + + switch (msg.what) { + case WifiScanner.CMD_START_SINGLE_SCAN: + mWifiMetrics.incrementOneshotScanCount(); + Bundle scanParams = (Bundle) msg.obj; + if (scanParams == null) { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); + return HANDLED; + } + scanParams.setDefusable(true); + ScanSettings scanSettings = + scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); + WorkSource workSource = + scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); + if (validateAndAddToScanQueue(ci, msg.arg2, scanSettings, workSource)) { + replySucceeded(msg); + // If were not currently scanning then try to start a scan. Otherwise + // this scan will be scheduled when transitioning back to IdleState + // after finishing the current scan. + if (getCurrentState() != mScanningState) { + tryToStartNewScan(); + } + } else { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1); + } + return HANDLED; + case WifiScanner.CMD_STOP_SINGLE_SCAN: + removeSingleScanRequest(ci, msg.arg2); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class IdleState extends State { + @Override + public void enter() { + tryToStartNewScan(); + } + + @Override + public boolean processMessage(Message msg) { + return NOT_HANDLED; + } + } + + class ScanningState extends State { + private WorkSource mScanWorkSource; + + @Override + public void enter() { + mScanWorkSource = mActiveScans.createMergedWorkSource(); + try { + mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource); + } catch (RemoteException e) { + loge(e.toString()); + } + } + + @Override + public void exit() { + try { + mBatteryStats.noteWifiScanStoppedFromSource(mScanWorkSource); + } catch (RemoteException e) { + loge(e.toString()); + } + + // if any scans are still active (never got results available then indicate failure) + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_UNKNOWN, + mActiveScans.size()); + sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, + "Scan was interrupted"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_SCAN_RESULTS_AVAILABLE: + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_SUCCESS, + mActiveScans.size()); + reportScanResults(mScannerImpl.getLatestSingleScanResults()); + mActiveScans.clear(); + transitionTo(mIdleState); + return HANDLED; + case CMD_FULL_SCAN_RESULTS: + reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); + return HANDLED; + case CMD_SCAN_FAILED: + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size()); + sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED, + "Scan failed"); + transitionTo(mIdleState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + boolean validateAndAddToScanQueue(ClientInfo ci, int handler, ScanSettings settings, + WorkSource workSource) { + if (ci == null) { + Log.d(TAG, "Failing single scan request ClientInfo not found " + handler); + return false; + } + if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) { + if (settings.channels == null || settings.channels.length == 0) { + Log.d(TAG, "Failing single scan because channel list was empty"); + return false; + } + } + logScanRequest("addSingleScanRequest", ci, handler, workSource, settings, null); + mPendingScans.add(new RequestInfo(ci, handler, workSource, settings)); + return true; + } + + void removeSingleScanRequest(ClientInfo ci, int handler) { + if (ci != null) { + logScanRequest("removeSingleScanRequest", ci, handler, null, null, null); + mPendingScans.removeAllForHandler(ci, handler); + mActiveScans.removeAllForHandler(ci, handler); + } + } + + void removeSingleScanRequests(ClientInfo ci) { + if (ci != null) { + logScanRequest("removeSingleScanRequests", ci, -1, null, null, null); + mPendingScans.removeAllForClient(ci); + mActiveScans.removeAllForClient(ci); + } + } + + void tryToStartNewScan() { + if (mPendingScans.size() == 0) { // no pending requests + return; + } + mChannelHelper.updateChannels(); + // TODO move merging logic to a scheduler + WifiNative.ScanSettings settings = new WifiNative.ScanSettings(); + settings.num_buckets = 1; + WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings(); + bucketSettings.bucket = 0; + bucketSettings.period_ms = 0; + bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; + + ChannelCollection channels = mChannelHelper.createChannelCollection(); + HashSet hiddenNetworkIdSet = new HashSet<>(); + for (RequestInfo entry : mPendingScans) { + channels.addChannels(entry.settings); + if (entry.settings.hiddenNetworkIds != null) { + for (int i = 0; i < entry.settings.hiddenNetworkIds.length; i++) { + hiddenNetworkIdSet.add(entry.settings.hiddenNetworkIds[i]); + } + } + if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) + != 0) { + bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; + } + } + if (hiddenNetworkIdSet.size() > 0) { + settings.hiddenNetworkIds = new int[hiddenNetworkIdSet.size()]; + int numHiddenNetworks = 0; + for (Integer hiddenNetworkId : hiddenNetworkIdSet) { + settings.hiddenNetworkIds[numHiddenNetworks++] = hiddenNetworkId; + } + } + + channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE); + + settings.buckets = new WifiNative.BucketSettings[] {bucketSettings}; + if (mScannerImpl.startSingleScan(settings, this)) { + // swap pending and active scan requests + RequestList tmp = mActiveScans; + mActiveScans = mPendingScans; + mPendingScans = tmp; + // make sure that the pending list is clear + mPendingScans.clear(); + transitionTo(mScanningState); + } else { + mWifiMetrics.incrementScanReturnEntry( + WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size()); + // notify and cancel failed scans + sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, + "Failed to start single scan"); + } + } + + void sendOpFailedToAllAndClear(RequestList clientHandlers, int reason, + String description) { + for (RequestInfo entry : clientHandlers) { + entry.reportEvent(WifiScanner.CMD_OP_FAILED, 0, + new WifiScanner.OperationResult(reason, description)); + } + clientHandlers.clear(); + } + + void reportFullScanResult(ScanResult result, int bucketsScanned) { + for (RequestInfo entry : mActiveScans) { + if (ScanScheduleUtil.shouldReportFullScanResultForSettings(mChannelHelper, + result, bucketsScanned, entry.settings, -1)) { + entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result); + } + } + } + + void reportScanResults(ScanData results) { + if (results != null && results.getResults() != null) { + if (results.getResults().length > 0) { + mWifiMetrics.incrementNonEmptyScanResultCount(); + } else { + mWifiMetrics.incrementEmptyScanResultCount(); + } + } + for (RequestInfo entry : mActiveScans) { + ScanData[] resultsArray = new ScanData[] {results}; + ScanData[] resultsToDeliver = ScanScheduleUtil.filterResultsForSettings( + mChannelHelper, resultsArray, entry.settings, -1); + WifiScanner.ParcelableScanData parcelableScanData = + new WifiScanner.ParcelableScanData(resultsToDeliver); + logCallback("singleScanResults", entry.clientInfo, entry.handlerId); + entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableScanData); + // make sure the handler is removed + entry.reportEvent(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, null); + } + } + } + + class WifiBackgroundScanStateMachine extends StateMachine + implements WifiNative.ScanEventHandler, WifiNative.HotlistEventHandler { + + private final DefaultState mDefaultState = new DefaultState(); + private final StartedState mStartedState = new StartedState(); + private final PausedState mPausedState = new PausedState(); + + private final ClientHandlerMap mActiveBackgroundScans = + new ClientHandlerMap() { + @Override + public ScanSettings put(ClientInfo ci, int handler, ScanSettings value) { + ScanSettings settings = put(Pair.create(ci, handler), value); + ci.reportScanWorkUpdate(); + return settings; + } + + @Override + public ScanSettings remove(ClientInfo ci, int handler) { + ScanSettings settings = remove(Pair.create(ci, handler)); + ci.reportScanWorkUpdate(); + return settings; + } + }; + private final ClientHandlerMap mActiveHotlistSettings = + new ClientHandlerMap<>(); + + WifiBackgroundScanStateMachine(Looper looper) { + super(TAG, looper); + + setLogRecSize(512); + setLogOnlyTransitions(false); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mStartedState, mDefaultState); + addState(mPausedState, mDefaultState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mDefaultState); + } + + public Collection getBackgroundScanSettings(ClientInfo ci) { + return mActiveBackgroundScans.getAllValues(ci); + } + + public Map getBackgroundScanSettingsHandlerMap(ClientInfo ci) { + return mActiveBackgroundScans.getHandlerToValueMap(ci); + } + + public void removeBackgroundScanSettings(ClientInfo ci) { + mActiveBackgroundScans.removeAll(ci); + updateSchedule(); + } + + public void removeHotlistSettings(ClientInfo ci) { + mActiveHotlistSettings.removeAll(ci); + resetHotlist(); + } + + @Override + public void onScanStatus(int event) { + if (DBG) localLog("onScanStatus event received, event=" + event); + switch(event) { + case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE: + case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS: + case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT: + sendMessage(CMD_SCAN_RESULTS_AVAILABLE); + break; + case WifiNative.WIFI_SCAN_FAILED: + sendMessage(CMD_SCAN_FAILED); + break; + default: + Log.e(TAG, "Unknown scan status event: " + event); + break; + } + } + + @Override + public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) { + if (DBG) localLog("onFullScanResult received"); + sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult); + } + + @Override + public void onScanPaused(ScanData scanData[]) { + if (DBG) localLog("onScanPaused received"); + sendMessage(CMD_SCAN_PAUSED, scanData); + } + + @Override + public void onScanRestarted() { + if (DBG) localLog("onScanRestarted received"); + sendMessage(CMD_SCAN_RESTARTED); + } + + @Override + public void onHotlistApFound(ScanResult[] results) { + if (DBG) localLog("onHotlistApFound event received"); + sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results); + } + + @Override + public void onHotlistApLost(ScanResult[] results) { + if (DBG) localLog("onHotlistApLost event received"); + sendMessage(CMD_HOTLIST_AP_LOST, 0, 0, results); + } + + class DefaultState extends State { + @Override + public void enter() { + if (DBG) localLog("DefaultState"); + mActiveBackgroundScans.clear(); + mActiveHotlistSettings.clear(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_DRIVER_LOADED: + // TODO this should be moved to a common location since it is used outside + // of this state machine. It is ok right now because the driver loaded event + // is sent to this state machine first. + if (mScannerImpl == null) { + mScannerImpl = mScannerImplFactory.create(mContext, mLooper); + mChannelHelper = mScannerImpl.getChannelHelper(); + } + + mScheduler = new BackgroundScanScheduler(mChannelHelper); + + WifiNative.ScanCapabilities capabilities = + new WifiNative.ScanCapabilities(); + if (!mScannerImpl.getScanCapabilities(capabilities)) { + loge("could not get scan capabilities"); + return HANDLED; + } + mScheduler.setMaxBuckets(capabilities.max_scan_buckets); + mScheduler.setMaxApPerScan(capabilities.max_ap_cache_per_scan); + + Log.i(TAG, "wifi driver loaded with scan capabilities: " + + "max buckets=" + capabilities.max_scan_buckets); + + transitionTo(mStartedState); + return HANDLED; + case CMD_DRIVER_UNLOADED: + Log.i(TAG, "wifi driver unloaded"); + transitionTo(mDefaultState); + break; + case WifiScanner.CMD_START_BACKGROUND_SCAN: + case WifiScanner.CMD_STOP_BACKGROUND_SCAN: + case WifiScanner.CMD_START_SINGLE_SCAN: + case WifiScanner.CMD_STOP_SINGLE_SCAN: + case WifiScanner.CMD_SET_HOTLIST: + case WifiScanner.CMD_RESET_HOTLIST: + case WifiScanner.CMD_GET_SCAN_RESULTS: + replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); + break; + + case CMD_SCAN_RESULTS_AVAILABLE: + if (DBG) localLog("ignored scan results available event"); + break; + + case CMD_FULL_SCAN_RESULTS: + if (DBG) localLog("ignored full scan result event"); + break; + + default: + break; + } + + return HANDLED; + } + } + + class StartedState extends State { + + @Override + public void enter() { + if (DBG) localLog("StartedState"); + } + + @Override + public void exit() { + sendBackgroundScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); + sendHotlistFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); + mScannerImpl.cleanup(); + } + + @Override + public boolean processMessage(Message msg) { + ClientInfo ci = mClients.get(msg.replyTo); + + switch (msg.what) { + case CMD_DRIVER_LOADED: + return NOT_HANDLED; + case CMD_DRIVER_UNLOADED: + return NOT_HANDLED; + case WifiScanner.CMD_START_BACKGROUND_SCAN: { + mWifiMetrics.incrementBackgroundScanCount(); + Bundle scanParams = (Bundle) msg.obj; + if (scanParams == null) { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); + return HANDLED; + } + scanParams.setDefusable(true); + ScanSettings scanSettings = + scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); + WorkSource workSource = + scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); + if (addBackgroundScanRequest(ci, msg.arg2, scanSettings, workSource)) { + replySucceeded(msg); + } else { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); + } + break; + } + case WifiScanner.CMD_STOP_BACKGROUND_SCAN: + removeBackgroundScanRequest(ci, msg.arg2); + break; + case WifiScanner.CMD_GET_SCAN_RESULTS: + reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); + replySucceeded(msg); + break; + case WifiScanner.CMD_SET_HOTLIST: + addHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj); + replySucceeded(msg); + break; + case WifiScanner.CMD_RESET_HOTLIST: + removeHotlist(ci, msg.arg2); + break; + case CMD_SCAN_RESULTS_AVAILABLE: + reportScanResults(mScannerImpl.getLatestBatchedScanResults(true)); + break; + case CMD_FULL_SCAN_RESULTS: + reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2); + break; + case CMD_HOTLIST_AP_FOUND: + reportHotlistResults(WifiScanner.CMD_AP_FOUND, (ScanResult[]) msg.obj); + break; + case CMD_HOTLIST_AP_LOST: + reportHotlistResults(WifiScanner.CMD_AP_LOST, (ScanResult[]) msg.obj); + break; + case CMD_SCAN_PAUSED: + reportScanResults((ScanData[]) msg.obj); + transitionTo(mPausedState); + break; + case CMD_SCAN_FAILED: + Log.e(TAG, "WifiScanner background scan gave CMD_SCAN_FAILED"); + sendBackgroundScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "Background Scan failed"); + break; + default: + return NOT_HANDLED; + } + + return HANDLED; + } + } + + class PausedState extends State { + @Override + public void enter() { + if (DBG) localLog("PausedState"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_SCAN_RESTARTED: + transitionTo(mStartedState); + break; + default: + deferMessage(msg); + break; + } + return HANDLED; + } + } + + private boolean addBackgroundScanRequest(ClientInfo ci, int handler, + ScanSettings settings, WorkSource workSource) { + // sanity check the input + if (ci == null) { + Log.d(TAG, "Failing scan request ClientInfo not found " + handler); + return false; + } + if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) { + loge("Failing scan request because periodInMs is " + settings.periodInMs + + ", min scan period is: " + WifiScanner.MIN_SCAN_PERIOD_MS); + return false; + } + + if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED && settings.channels == null) { + loge("Channels was null with unspecified band"); + return false; + } + + if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED + && settings.channels.length == 0) { + loge("No channels specified"); + return false; + } + + int minSupportedPeriodMs = mChannelHelper.estimateScanDuration(settings); + if (settings.periodInMs < minSupportedPeriodMs) { + loge("Failing scan request because minSupportedPeriodMs is " + + minSupportedPeriodMs + " but the request wants " + settings.periodInMs); + return false; + } + + // check truncated binary exponential back off scan settings + if (settings.maxPeriodInMs != 0 && settings.maxPeriodInMs != settings.periodInMs) { + if (settings.maxPeriodInMs < settings.periodInMs) { + loge("Failing scan request because maxPeriodInMs is " + settings.maxPeriodInMs + + " but less than periodInMs " + settings.periodInMs); + return false; + } + if (settings.maxPeriodInMs > WifiScanner.MAX_SCAN_PERIOD_MS) { + loge("Failing scan request because maxSupportedPeriodMs is " + + WifiScanner.MAX_SCAN_PERIOD_MS + " but the request wants " + + settings.maxPeriodInMs); + return false; + } + if (settings.stepCount < 1) { + loge("Failing scan request because stepCount is " + settings.stepCount + + " which is less than 1"); + return false; + } + } + + logScanRequest("addBackgroundScanRequest", ci, handler, null, settings, null); + // TODO(b/27903217): Blame scan on provided work source + mActiveBackgroundScans.put(ci, handler, settings); + + if (updateSchedule()) { + return true; + } else { + mActiveBackgroundScans.remove(ci, handler); + localLog("Failing scan request because failed to reset scan"); + return false; + } + } + + private boolean updateSchedule() { + mChannelHelper.updateChannels(); + Collection settings = mActiveBackgroundScans.values(); + + mScheduler.updateSchedule(settings); + WifiNative.ScanSettings schedule = mScheduler.getSchedule(); + + if (ScanScheduleUtil.scheduleEquals(mPreviousSchedule, schedule)) { + if (DBG) Log.d(TAG, "schedule updated with no change"); + return true; + } + + mPreviousSchedule = schedule; + + if (schedule.num_buckets == 0) { + mScannerImpl.stopBatchedScan(); + if (DBG) Log.d(TAG, "scan stopped"); + return true; + } else { + Log.d(TAG, "starting scan: " + + "base period=" + schedule.base_period_ms + + ", max ap per scan=" + schedule.max_ap_per_scan + + ", batched scans=" + schedule.report_threshold_num_scans); + for (int b = 0; b < schedule.num_buckets; b++) { + WifiNative.BucketSettings bucket = schedule.buckets[b]; + Log.d(TAG, "bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" + + "[" + bucket.report_events + "]: " + + ChannelHelper.toString(bucket)); + } + + if (mScannerImpl.startBatchedScan(schedule, this)) { + if (DBG) { + Log.d(TAG, "scan restarted with " + schedule.num_buckets + + " bucket(s) and base period: " + schedule.base_period_ms); + } + return true; + } else { + mPreviousSchedule = null; + loge("error starting scan: " + + "base period=" + schedule.base_period_ms + + ", max ap per scan=" + schedule.max_ap_per_scan + + ", batched scans=" + schedule.report_threshold_num_scans); + for (int b = 0; b < schedule.num_buckets; b++) { + WifiNative.BucketSettings bucket = schedule.buckets[b]; + loge("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)" + + "[" + bucket.report_events + "]: " + + ChannelHelper.toString(bucket)); + } + return false; + } + } + } + + private void removeBackgroundScanRequest(ClientInfo ci, int handler) { + if (ci != null) { + ScanSettings settings = mActiveBackgroundScans.remove(ci, handler); + logScanRequest("removeBackgroundScanRequest", ci, handler, null, settings, null); + updateSchedule(); + } + } + + private void reportFullScanResult(ScanResult result, int bucketsScanned) { + for (Map.Entry, ScanSettings> entry + : mActiveBackgroundScans.entrySet()) { + ClientInfo ci = entry.getKey().first; + int handler = entry.getKey().second; + ScanSettings settings = entry.getValue(); + if (mScheduler.shouldReportFullScanResultForSettings( + result, bucketsScanned, settings)) { + ScanResult newResult = new ScanResult(result); + if (result.informationElements != null) { + newResult.informationElements = result.informationElements.clone(); + } + else { + newResult.informationElements = null; + } + ci.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, handler, newResult); + } + } + } + + private void reportScanResults(ScanData[] results) { + for (ScanData result : results) { + if (result != null && result.getResults() != null) { + if (result.getResults().length > 0) { + mWifiMetrics.incrementNonEmptyScanResultCount(); + } else { + mWifiMetrics.incrementEmptyScanResultCount(); + } + } + } + for (Map.Entry, ScanSettings> entry + : mActiveBackgroundScans.entrySet()) { + ClientInfo ci = entry.getKey().first; + int handler = entry.getKey().second; + ScanSettings settings = entry.getValue(); + ScanData[] resultsToDeliver = + mScheduler.filterResultsForSettings(results, settings); + if (resultsToDeliver != null) { + logCallback("backgroundScanResults", ci, handler); + WifiScanner.ParcelableScanData parcelableScanData = + new WifiScanner.ParcelableScanData(resultsToDeliver); + ci.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanData); + } + } + } + + private void sendBackgroundScanFailedToAllAndClear(int reason, String description) { + for (Pair key : mActiveBackgroundScans.keySet()) { + ClientInfo ci = key.first; + int handler = key.second; + ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, + new WifiScanner.OperationResult(reason, description)); + } + mActiveBackgroundScans.clear(); + } + + private void addHotlist(ClientInfo ci, int handler, WifiScanner.HotlistSettings settings) { + mActiveHotlistSettings.put(ci, handler, settings); + resetHotlist(); + } + + private void removeHotlist(ClientInfo ci, int handler) { + mActiveHotlistSettings.remove(ci, handler); + resetHotlist(); + } + + private void resetHotlist() { + Collection settings = mActiveHotlistSettings.values(); + int num_hotlist_ap = 0; + + for (WifiScanner.HotlistSettings s : settings) { + num_hotlist_ap += s.bssidInfos.length; + } + + if (num_hotlist_ap == 0) { + mScannerImpl.resetHotlist(); + } else { + BssidInfo[] bssidInfos = new BssidInfo[num_hotlist_ap]; + int apLostThreshold = Integer.MAX_VALUE; + int index = 0; + for (WifiScanner.HotlistSettings s : settings) { + for (int i = 0; i < s.bssidInfos.length; i++, index++) { + bssidInfos[index] = s.bssidInfos[i]; + } + if (s.apLostThreshold < apLostThreshold) { + apLostThreshold = s.apLostThreshold; + } + } + + WifiScanner.HotlistSettings mergedSettings = new WifiScanner.HotlistSettings(); + mergedSettings.bssidInfos = bssidInfos; + mergedSettings.apLostThreshold = apLostThreshold; + mScannerImpl.setHotlist(mergedSettings, this); + } + } + + private void reportHotlistResults(int what, ScanResult[] results) { + if (DBG) localLog("reportHotlistResults " + what + " results " + results.length); + for (Map.Entry, WifiScanner.HotlistSettings> entry + : mActiveHotlistSettings.entrySet()) { + ClientInfo ci = entry.getKey().first; + int handler = entry.getKey().second; + WifiScanner.HotlistSettings settings = entry.getValue(); + int num_results = 0; + for (ScanResult result : results) { + for (BssidInfo BssidInfo : settings.bssidInfos) { + if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) { + num_results++; + break; + } + } + } + if (num_results == 0) { + // nothing to report + return; + } + ScanResult[] results2 = new ScanResult[num_results]; + int index = 0; + for (ScanResult result : results) { + for (BssidInfo BssidInfo : settings.bssidInfos) { + if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) { + results2[index] = result; + index++; + } + } + } + WifiScanner.ParcelableScanResults parcelableScanResults = + new WifiScanner.ParcelableScanResults(results2); + + ci.reportEvent(what, 0, handler, parcelableScanResults); + } + } + + private void sendHotlistFailedToAllAndClear(int reason, String description) { + for (Pair key : mActiveHotlistSettings.keySet()) { + ClientInfo ci = key.first; + int handler = key.second; + ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, + new WifiScanner.OperationResult(reason, description)); + } + mActiveHotlistSettings.clear(); + } + } + + /** + * PNO scan state machine has 5 states: + * -Default State + * -Started State + * -Hw Pno Scan state + * -Single Scan state + * -Sw Pno Scan state + * + * These are the main state transitions: + * 1. Start at |Default State| + * 2. Move to |Started State| when we get the |WIFI_SCAN_AVAILABLE| broadcast from WifiManager. + * 3. When a new PNO scan request comes in: + * a.1. Switch to |Hw Pno Scan state| when the device supports HW PNO + * (This could either be HAL based ePNO or supplicant based PNO). + * a.2. In |Hw Pno Scan state| when PNO scan results are received, check if the result + * contains IE (information elements). If yes, send the results to the client, else + * switch to |Single Scan state| and send the result to the client when the scan result + * is obtained. + * b.1. Switch to |Sw Pno Scan state| when the device does not supports HW PNO + * (This is for older devices which do not support HW PNO and for connected PNO on + * devices which support supplicant based PNO) + * b.2. In |Sw Pno Scan state| send the result to the client when the background scan result + * is obtained + * + * Note: PNO scans only work for a single client today. We don't have support in HW to support + * multiple requests at the same time, so will need non-trivial changes to support (if at all + * possible) in WifiScanningService. + */ + class WifiPnoScanStateMachine extends StateMachine implements WifiNative.PnoEventHandler { + + private final DefaultState mDefaultState = new DefaultState(); + private final StartedState mStartedState = new StartedState(); + private final HwPnoScanState mHwPnoScanState = new HwPnoScanState(); + private final SwPnoScanState mSwPnoScanState = new SwPnoScanState(); + private final SingleScanState mSingleScanState = new SingleScanState(); + private InternalClientInfo mInternalClientInfo; + + private final ClientHandlerMap> mActivePnoScans = + new ClientHandlerMap<>(); + + WifiPnoScanStateMachine(Looper looper) { + super(TAG, looper); + + setLogRecSize(512); + setLogOnlyTransitions(false); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mStartedState, mDefaultState); + addState(mHwPnoScanState, mStartedState); + addState(mSingleScanState, mHwPnoScanState); + addState(mSwPnoScanState, mStartedState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mDefaultState); + } + + public void removePnoSettings(ClientInfo ci) { + mActivePnoScans.removeAll(ci); + transitionTo(mStartedState); + } + + @Override + public void onPnoNetworkFound(ScanResult[] results) { + if (DBG) localLog("onWifiPnoNetworkFound event received"); + sendMessage(CMD_PNO_NETWORK_FOUND, 0, 0, results); + } + + @Override + public void onPnoScanFailed() { + if (DBG) localLog("onWifiPnoScanFailed event received"); + sendMessage(CMD_PNO_SCAN_FAILED, 0, 0, null); + } + + class DefaultState extends State { + @Override + public void enter() { + if (DBG) localLog("DefaultState"); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_DRIVER_LOADED: + transitionTo(mStartedState); + break; + case CMD_DRIVER_UNLOADED: + transitionTo(mDefaultState); + break; + case WifiScanner.CMD_START_PNO_SCAN: + case WifiScanner.CMD_STOP_PNO_SCAN: + replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); + break; + case CMD_PNO_NETWORK_FOUND: + case CMD_PNO_SCAN_FAILED: + case WifiScanner.CMD_SCAN_RESULT: + case WifiScanner.CMD_OP_FAILED: + loge("Unexpected message " + msg.what); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class StartedState extends State { + @Override + public void enter() { + if (DBG) localLog("StartedState"); + } + + @Override + public void exit() { + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); + } + + @Override + public boolean processMessage(Message msg) { + ClientInfo ci = mClients.get(msg.replyTo); + switch (msg.what) { + case WifiScanner.CMD_START_PNO_SCAN: + Bundle pnoParams = (Bundle) msg.obj; + if (pnoParams == null) { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); + return HANDLED; + } + pnoParams.setDefusable(true); + PnoSettings pnoSettings = + pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); + // This message is handled after the transition to SwPnoScan/HwPnoScan state + deferMessage(msg); + if (mScannerImpl.isHwPnoSupported(pnoSettings.isConnected)) { + transitionTo(mHwPnoScanState); + } else { + transitionTo(mSwPnoScanState); + } + break; + case WifiScanner.CMD_STOP_PNO_SCAN: + replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "no scan running"); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class HwPnoScanState extends State { + @Override + public void enter() { + if (DBG) localLog("HwPnoScanState"); + } + + @Override + public void exit() { + // Reset PNO scan in ScannerImpl before we exit. + mScannerImpl.resetHwPnoList(); + removeInternalClient(); + } + + @Override + public boolean processMessage(Message msg) { + ClientInfo ci = mClients.get(msg.replyTo); + switch (msg.what) { + case WifiScanner.CMD_START_PNO_SCAN: + Bundle pnoParams = (Bundle) msg.obj; + if (pnoParams == null) { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); + return HANDLED; + } + pnoParams.setDefusable(true); + PnoSettings pnoSettings = + pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); + ScanSettings scanSettings = + pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY); + if (addHwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) { + replySucceeded(msg); + } else { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); + transitionTo(mStartedState); + } + break; + case WifiScanner.CMD_STOP_PNO_SCAN: + removeHwPnoScanRequest(ci, msg.arg2); + transitionTo(mStartedState); + break; + case CMD_PNO_NETWORK_FOUND: + ScanResult[] scanResults = ((ScanResult[]) msg.obj); + if (isSingleScanNeeded(scanResults)) { + addSingleScanRequest(getScanSettings()); + transitionTo(mSingleScanState); + } else { + reportPnoNetworkFound((ScanResult[]) msg.obj); + } + break; + case CMD_PNO_SCAN_FAILED: + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "pno scan failed"); + transitionTo(mStartedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class SingleScanState extends State { + @Override + public void enter() { + if (DBG) localLog("SingleScanState"); + } + + @Override + public boolean processMessage(Message msg) { + ClientInfo ci = mClients.get(msg.replyTo); + switch (msg.what) { + case WifiScanner.CMD_SCAN_RESULT: + WifiScanner.ParcelableScanData parcelableScanData = + (WifiScanner.ParcelableScanData) msg.obj; + ScanData[] scanDatas = parcelableScanData.getResults(); + ScanData lastScanData = scanDatas[scanDatas.length - 1]; + reportPnoNetworkFound(lastScanData.getResults()); + transitionTo(mHwPnoScanState); + break; + case WifiScanner.CMD_OP_FAILED: + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "single scan failed"); + transitionTo(mStartedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class SwPnoScanState extends State { + private final ArrayList mSwPnoFullScanResults = new ArrayList<>(); + + @Override + public void enter() { + if (DBG) localLog("SwPnoScanState"); + mSwPnoFullScanResults.clear(); + } + + @Override + public void exit() { + removeInternalClient(); + } + + @Override + public boolean processMessage(Message msg) { + ClientInfo ci = mClients.get(msg.replyTo); + switch (msg.what) { + case WifiScanner.CMD_START_PNO_SCAN: + Bundle pnoParams = (Bundle) msg.obj; + if (pnoParams == null) { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); + return HANDLED; + } + pnoParams.setDefusable(true); + PnoSettings pnoSettings = + pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY); + ScanSettings scanSettings = + pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY); + if (addSwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) { + replySucceeded(msg); + } else { + replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); + transitionTo(mStartedState); + } + break; + case WifiScanner.CMD_STOP_PNO_SCAN: + removeSwPnoScanRequest(ci, msg.arg2); + transitionTo(mStartedState); + break; + case WifiScanner.CMD_FULL_SCAN_RESULT: + // Aggregate full scan results until we get the |CMD_SCAN_RESULT| message + mSwPnoFullScanResults.add((ScanResult) msg.obj); + break; + case WifiScanner.CMD_SCAN_RESULT: + ScanResult[] scanResults = mSwPnoFullScanResults.toArray( + new ScanResult[mSwPnoFullScanResults.size()]); + reportPnoNetworkFound(scanResults); + mSwPnoFullScanResults.clear(); + break; + case WifiScanner.CMD_OP_FAILED: + sendPnoScanFailedToAllAndClear( + WifiScanner.REASON_UNSPECIFIED, "background scan failed"); + transitionTo(mStartedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private WifiNative.PnoSettings convertPnoSettingsToNative(PnoSettings pnoSettings) { + WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings(); + nativePnoSetting.min5GHzRssi = pnoSettings.min5GHzRssi; + nativePnoSetting.min24GHzRssi = pnoSettings.min24GHzRssi; + nativePnoSetting.initialScoreMax = pnoSettings.initialScoreMax; + nativePnoSetting.currentConnectionBonus = pnoSettings.currentConnectionBonus; + nativePnoSetting.sameNetworkBonus = pnoSettings.sameNetworkBonus; + nativePnoSetting.secureBonus = pnoSettings.secureBonus; + nativePnoSetting.band5GHzBonus = pnoSettings.band5GHzBonus; + nativePnoSetting.isConnected = pnoSettings.isConnected; + nativePnoSetting.networkList = + new WifiNative.PnoNetwork[pnoSettings.networkList.length]; + for (int i = 0; i < pnoSettings.networkList.length; i++) { + nativePnoSetting.networkList[i] = new WifiNative.PnoNetwork(); + nativePnoSetting.networkList[i].ssid = pnoSettings.networkList[i].ssid; + nativePnoSetting.networkList[i].networkId = pnoSettings.networkList[i].networkId; + nativePnoSetting.networkList[i].priority = pnoSettings.networkList[i].priority; + nativePnoSetting.networkList[i].flags = pnoSettings.networkList[i].flags; + nativePnoSetting.networkList[i].auth_bit_field = + pnoSettings.networkList[i].authBitField; + } + return nativePnoSetting; + } + + // Retrieve the active PNO settings. + private PnoSettings getPnoSettings() { + return mActivePnoScans.entrySet().iterator().next().getValue().first; + } + + // Retrieve the active scan settings. + private ScanSettings getScanSettings() { + return mActivePnoScans.entrySet().iterator().next().getValue().second; + } + + private void removeInternalClient() { + if (mInternalClientInfo != null) { + mInternalClientInfo.cleanup(); + mInternalClientInfo = null; + } else { + Log.w(TAG, "No Internal client for PNO"); + } + } + + private void addInternalClient(ClientInfo ci) { + if (mInternalClientInfo == null) { + mInternalClientInfo = + new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler())); + mInternalClientInfo.register(); + } else { + Log.w(TAG, "Internal client for PNO already exists"); + } + } + + private void addPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, + PnoSettings pnoSettings) { + mActivePnoScans.put(ci, handler, Pair.create(pnoSettings, scanSettings)); + addInternalClient(ci); + } + + private Pair removePnoScanRequest(ClientInfo ci, int handler) { + Pair settings = mActivePnoScans.remove(ci, handler); + return settings; + } + + private boolean addHwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, + PnoSettings pnoSettings) { + if (ci == null) { + Log.d(TAG, "Failing scan request ClientInfo not found " + handler); + return false; + } + if (!mActivePnoScans.isEmpty()) { + loge("Failing scan request because there is already an active scan"); + return false; + } + WifiNative.PnoSettings nativePnoSettings = convertPnoSettingsToNative(pnoSettings); + if (!mScannerImpl.setHwPnoList(nativePnoSettings, mPnoScanStateMachine)) { + return false; + } + logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings); + addPnoScanRequest(ci, handler, scanSettings, pnoSettings); + // HW PNO is supported, check if we need a background scan running for this. + if (mScannerImpl.shouldScheduleBackgroundScanForHwPno()) { + addBackgroundScanRequest(scanSettings); + } + return true; + } + + private void removeHwPnoScanRequest(ClientInfo ci, int handler) { + if (ci != null) { + Pair settings = removePnoScanRequest(ci, handler); + logScanRequest("removeHwPnoScanRequest", ci, handler, null, + settings.second, settings.first); + } + } + + private boolean addSwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings, + PnoSettings pnoSettings) { + if (ci == null) { + Log.d(TAG, "Failing scan request ClientInfo not found " + handler); + return false; + } + if (!mActivePnoScans.isEmpty()) { + loge("Failing scan request because there is already an active scan"); + return false; + } + logScanRequest("addSwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings); + addPnoScanRequest(ci, handler, scanSettings, pnoSettings); + // HW PNO is not supported, we need to revert to normal background scans and + // report events after each scan and we need full scan results to get the IE information + scanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN + | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; + addBackgroundScanRequest(scanSettings); + return true; + } + + private void removeSwPnoScanRequest(ClientInfo ci, int handler) { + if (ci != null) { + Pair settings = removePnoScanRequest(ci, handler); + logScanRequest("removeSwPnoScanRequest", ci, handler, null, + settings.second, settings.first); + } + } + + private void reportPnoNetworkFound(ScanResult[] results) { + WifiScanner.ParcelableScanResults parcelableScanResults = + new WifiScanner.ParcelableScanResults(results); + for (Map.Entry, Pair> entry + : mActivePnoScans.entrySet()) { + ClientInfo ci = entry.getKey().first; + int handler = entry.getKey().second; + logCallback("pnoNetworkFound", ci, handler); + ci.reportEvent( + WifiScanner.CMD_PNO_NETWORK_FOUND, 0, handler, parcelableScanResults); + } + } + + private void sendPnoScanFailedToAllAndClear(int reason, String description) { + for (Pair key : mActivePnoScans.keySet()) { + ClientInfo ci = key.first; + int handler = key.second; + ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler, + new WifiScanner.OperationResult(reason, description)); + } + mActivePnoScans.clear(); + } + + private void addBackgroundScanRequest(ScanSettings settings) { + if (DBG) localLog("Starting background scan"); + if (mInternalClientInfo != null) { + mInternalClientInfo.sendRequestToClientHandler( + WifiScanner.CMD_START_BACKGROUND_SCAN, settings, + WifiStateMachine.WIFI_WORK_SOURCE); + } + } + + private void addSingleScanRequest(ScanSettings settings) { + if (DBG) localLog("Starting single scan"); + if (mInternalClientInfo != null) { + mInternalClientInfo.sendRequestToClientHandler( + WifiScanner.CMD_START_SINGLE_SCAN, settings, + WifiStateMachine.WIFI_WORK_SOURCE); + } + } + + /** + * Checks if IE are present in scan data, if no single scan is needed to report event to + * client + */ + private boolean isSingleScanNeeded(ScanResult[] scanResults) { + for (ScanResult scanResult : scanResults) { + if (scanResult.informationElements != null + && scanResult.informationElements.length > 0) { + return false; + } + } + return true; + } + } + + private abstract class ClientInfo { + private final int mUid; + private final WorkSource mWorkSource; + private boolean mScanWorkReported = false; + protected final Messenger mMessenger; + + ClientInfo(int uid, Messenger messenger) { + mUid = uid; + mMessenger = messenger; + mWorkSource = new WorkSource(uid); + } + + /** + * Register this client to main client map. + */ + public void register() { + mClients.put(mMessenger, this); + } + + /** + * Unregister this client from main client map. + */ + private void unregister() { + mClients.remove(mMessenger); + } + + public void cleanup() { + mSingleScanStateMachine.removeSingleScanRequests(this); + mBackgroundScanStateMachine.removeBackgroundScanSettings(this); + mBackgroundScanStateMachine.removeHotlistSettings(this); + unregister(); + localLog("Successfully stopped all requests for client " + this); + } + + public int getUid() { + return mUid; + } + + public void reportEvent(int what, int arg1, int arg2) { + reportEvent(what, arg1, arg2, null); + } + + // This has to be implemented by subclasses to report events back to clients. + public abstract void reportEvent(int what, int arg1, int arg2, Object obj); + + private void reportBatchedScanStart() { + if (mUid == 0) + return; + + int csph = getCsph(); + + try { + mBatteryStats.noteWifiBatchedScanStartedFromSource(mWorkSource, csph); + } catch (RemoteException e) { + logw("failed to report scan work: " + e.toString()); + } + } + + private void reportBatchedScanStop() { + if (mUid == 0) + return; + + try { + mBatteryStats.noteWifiBatchedScanStoppedFromSource(mWorkSource); + } catch (RemoteException e) { + logw("failed to cleanup scan work: " + e.toString()); + } + } + + // TODO migrate batterystats to accept scan duration per hour instead of csph + private int getCsph() { + int totalScanDurationPerHour = 0; + Collection settingsList = + mBackgroundScanStateMachine.getBackgroundScanSettings(this); + for (ScanSettings settings : settingsList) { + int scanDurationMs = mChannelHelper.estimateScanDuration(settings); + int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / + settings.periodInMs; + totalScanDurationPerHour += scanDurationMs * scans_per_Hour; + } + + return totalScanDurationPerHour / ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS; + } + + public void reportScanWorkUpdate() { + if (mScanWorkReported) { + reportBatchedScanStop(); + mScanWorkReported = false; + } + if (mBackgroundScanStateMachine.getBackgroundScanSettings(this).isEmpty()) { + reportBatchedScanStart(); + mScanWorkReported = true; + } + } + + @Override + public String toString() { + return "ClientInfo[uid=" + mUid + "]"; + } + } + + /** + * This class is used to represent external clients to the WifiScanning Service. + */ + private class ExternalClientInfo extends ClientInfo { + private final AsyncChannel mChannel; + /** + * Indicates if the client is still connected + * If the client is no longer connected then messages to it will be silently dropped + */ + private boolean mDisconnected = false; + + ExternalClientInfo(int uid, Messenger messenger, AsyncChannel c) { + super(uid, messenger); + mChannel = c; + if (DBG) localLog("New client, channel: " + c); + } + + @Override + public void reportEvent(int what, int arg1, int arg2, Object obj) { + if (!mDisconnected) { + mChannel.sendMessage(what, arg1, arg2, obj); + } + } + + @Override + public void cleanup() { + mDisconnected = true; + // Internal clients should not have any wifi change requests. So, keeping this cleanup + // only for external client because this will otherwise cause an infinite recursion + // when the internal client in WifiChangeStateMachine is cleaned up. + mWifiChangeStateMachine.removeWifiChangeHandler(this); + mPnoScanStateMachine.removePnoSettings(this); + super.cleanup(); + } + } + + /** + * This class is used to represent internal clients to the WifiScanning Service. This is needed + * for communicating between State Machines. + * This leaves the onReportEvent method unimplemented, so that the clients have the freedom + * to handle the events as they need. + */ + private class InternalClientInfo extends ClientInfo { + private static final int INTERNAL_CLIENT_HANDLER = 0; + + /** + * The UID here is used to proxy the original external requester UID. + */ + InternalClientInfo(int requesterUid, Messenger messenger) { + super(requesterUid, messenger); + } + + @Override + public void reportEvent(int what, int arg1, int arg2, Object obj) { + Message message = Message.obtain(); + message.what = what; + message.arg1 = arg1; + message.arg2 = arg2; + message.obj = obj; + try { + mMessenger.send(message); + } catch (RemoteException e) { + loge("Failed to send message: " + what); + } + } + + /** + * Send a message to the client handler which should reroute the message to the appropriate + * state machine. + */ + public void sendRequestToClientHandler(int what, ScanSettings settings, + WorkSource workSource) { + Message msg = Message.obtain(); + msg.what = what; + msg.arg2 = INTERNAL_CLIENT_HANDLER; + if (settings != null) { + Bundle bundle = new Bundle(); + bundle.putParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY, settings); + bundle.putParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY, workSource); + msg.obj = bundle; + } + msg.replyTo = mMessenger; + msg.sendingUid = getUid(); + mClientHandler.sendMessage(msg); + } + + /** + * Send a message to the client handler which should reroute the message to the appropriate + * state machine. + */ + public void sendRequestToClientHandler(int what) { + sendRequestToClientHandler(what, null, null); + } + } + + void replySucceeded(Message msg) { + if (msg.replyTo != null) { + Message reply = Message.obtain(); + reply.what = WifiScanner.CMD_OP_SUCCEEDED; + reply.arg2 = msg.arg2; + try { + msg.replyTo.send(reply); + } catch (RemoteException e) { + // There's not much we can do if reply can't be sent! + } + } else { + // locally generated message; doesn't need a reply! + } + } + + void replyFailed(Message msg, int reason, String description) { + if (msg.replyTo != null) { + Message reply = Message.obtain(); + reply.what = WifiScanner.CMD_OP_FAILED; + reply.arg2 = msg.arg2; + reply.obj = new WifiScanner.OperationResult(reason, description); + try { + msg.replyTo.send(reply); + } catch (RemoteException e) { + // There's not much we can do if reply can't be sent! + } + } else { + // locally generated message; doesn't need a reply! + } + } + + /** + * Wifi Change state machine is used to handle any wifi change tracking requests. + * TODO: This state machine doesn't handle driver loading/unloading yet. + */ + class WifiChangeStateMachine extends StateMachine + implements WifiNative.SignificantWifiChangeEventHandler { + + private static final int MAX_APS_TO_TRACK = 3; + private static final int MOVING_SCAN_PERIOD_MS = 10000; + private static final int STATIONARY_SCAN_PERIOD_MS = 5000; + private static final int MOVING_STATE_TIMEOUT_MS = 30000; + + State mDefaultState = new DefaultState(); + State mStationaryState = new StationaryState(); + State mMovingState = new MovingState(); + + private static final String ACTION_TIMEOUT = + "com.android.server.WifiScanningServiceImpl.action.TIMEOUT"; + private PendingIntent mTimeoutIntent; + private ScanResult[] mCurrentBssids; + private InternalClientInfo mInternalClientInfo; + + private final Set> mActiveWifiChangeHandlers = new HashSet<>(); + + WifiChangeStateMachine(Looper looper) { + super("SignificantChangeStateMachine", looper); + + // CHECKSTYLE:OFF IndentationCheck + addState(mDefaultState); + addState(mStationaryState, mDefaultState); + addState(mMovingState, mDefaultState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mDefaultState); + } + + public void removeWifiChangeHandler(ClientInfo ci) { + Iterator> iter = mActiveWifiChangeHandlers.iterator(); + while (iter.hasNext()) { + Pair entry = iter.next(); + if (entry.first == ci) { + iter.remove(); + } + } + untrackSignificantWifiChangeOnEmpty(); + } + + class DefaultState extends State { + @Override + public void enter() { + if (DBG) localLog("Entering IdleState"); + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) localLog("DefaultState state got " + msg); + ClientInfo ci = mClients.get(msg.replyTo); + switch (msg.what) { + case WifiScanner.CMD_START_TRACKING_CHANGE: + addWifiChangeHandler(ci, msg.arg2); + replySucceeded(msg); + transitionTo(mMovingState); + break; + case WifiScanner.CMD_STOP_TRACKING_CHANGE: + // nothing to do + break; + case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: + /* save configuration till we transition to moving state */ + deferMessage(msg); + break; + case WifiScanner.CMD_SCAN_RESULT: + // nothing to do + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class StationaryState extends State { + @Override + public void enter() { + if (DBG) localLog("Entering StationaryState"); + reportWifiStabilized(mCurrentBssids); + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) localLog("Stationary state got " + msg); + ClientInfo ci = mClients.get(msg.replyTo); + switch (msg.what) { + case WifiScanner.CMD_START_TRACKING_CHANGE: + addWifiChangeHandler(ci, msg.arg2); + replySucceeded(msg); + break; + case WifiScanner.CMD_STOP_TRACKING_CHANGE: + removeWifiChangeHandler(ci, msg.arg2); + break; + case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: + /* save configuration till we transition to moving state */ + deferMessage(msg); + break; + case CMD_WIFI_CHANGE_DETECTED: + if (DBG) localLog("Got wifi change detected"); + reportWifiChanged((ScanResult[]) msg.obj); + transitionTo(mMovingState); + break; + case WifiScanner.CMD_SCAN_RESULT: + // nothing to do + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class MovingState extends State { + boolean mWifiChangeDetected = false; + boolean mScanResultsPending = false; + + @Override + public void enter() { + if (DBG) localLog("Entering MovingState"); + if (mTimeoutIntent == null) { + Intent intent = new Intent(ACTION_TIMEOUT, null); + mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + sendMessage(CMD_WIFI_CHANGE_TIMEOUT); + } + }, new IntentFilter(ACTION_TIMEOUT)); + } + issueFullScan(); + } + + @Override + public boolean processMessage(Message msg) { + if (DBG) localLog("MovingState state got " + msg); + ClientInfo ci = mClients.get(msg.replyTo); + switch (msg.what) { + case WifiScanner.CMD_START_TRACKING_CHANGE: + addWifiChangeHandler(ci, msg.arg2); + replySucceeded(msg); + break; + case WifiScanner.CMD_STOP_TRACKING_CHANGE: + removeWifiChangeHandler(ci, msg.arg2); + break; + case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE: + if (DBG) localLog("Got configuration from app"); + WifiScanner.WifiChangeSettings settings = + (WifiScanner.WifiChangeSettings) msg.obj; + reconfigureScan(settings); + mWifiChangeDetected = false; + long unchangedDelay = settings.unchangedSampleSize * settings.periodInMs; + mAlarmManager.cancel(mTimeoutIntent); + mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + unchangedDelay, + mTimeoutIntent); + break; + case WifiScanner.CMD_SCAN_RESULT: + if (DBG) localLog("Got scan results"); + if (mScanResultsPending) { + if (DBG) localLog("reconfiguring scan"); + reconfigureScan((ScanData[])msg.obj, + STATIONARY_SCAN_PERIOD_MS); + mWifiChangeDetected = false; + mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + MOVING_STATE_TIMEOUT_MS, + mTimeoutIntent); + mScanResultsPending = false; + } + break; + case CMD_WIFI_CHANGE_DETECTED: + if (DBG) localLog("Change detected"); + mAlarmManager.cancel(mTimeoutIntent); + reportWifiChanged((ScanResult[])msg.obj); + mWifiChangeDetected = true; + issueFullScan(); + break; + case CMD_WIFI_CHANGE_TIMEOUT: + if (DBG) localLog("Got timeout event"); + if (mWifiChangeDetected == false) { + transitionTo(mStationaryState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + @Override + public void exit() { + mAlarmManager.cancel(mTimeoutIntent); + } + + void issueFullScan() { + if (DBG) localLog("Issuing full scan"); + ScanSettings settings = new ScanSettings(); + settings.band = WifiScanner.WIFI_BAND_BOTH; + settings.periodInMs = MOVING_SCAN_PERIOD_MS; + settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; + addScanRequest(settings); + mScanResultsPending = true; + } + + } + + private void reconfigureScan(ScanData[] results, int period) { + // find brightest APs and set them as sentinels + if (results.length < MAX_APS_TO_TRACK) { + localLog("too few APs (" + results.length + ") available to track wifi change"); + return; + } + + removeScanRequest(); + + // remove duplicate BSSIDs + HashMap bssidToScanResult = new HashMap(); + for (ScanResult result : results[0].getResults()) { + ScanResult saved = bssidToScanResult.get(result.BSSID); + if (saved == null) { + bssidToScanResult.put(result.BSSID, result); + } else if (saved.level > result.level) { + bssidToScanResult.put(result.BSSID, result); + } + } + + // find brightest BSSIDs + ScanResult brightest[] = new ScanResult[MAX_APS_TO_TRACK]; + Collection results2 = bssidToScanResult.values(); + for (ScanResult result : results2) { + for (int j = 0; j < brightest.length; j++) { + if (brightest[j] == null + || (brightest[j].level < result.level)) { + for (int k = brightest.length; k > (j + 1); k--) { + brightest[k - 1] = brightest[k - 2]; + } + brightest[j] = result; + break; + } + } + } + + // Get channels to scan for + ArrayList channels = new ArrayList(); + for (int i = 0; i < brightest.length; i++) { + boolean found = false; + for (int j = i + 1; j < brightest.length; j++) { + if (brightest[j].frequency == brightest[i].frequency) { + found = true; + } + } + if (!found) { + channels.add(brightest[i].frequency); + } + } + + if (DBG) localLog("Found " + channels.size() + " channels"); + + // set scanning schedule + ScanSettings settings = new ScanSettings(); + settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; + settings.channels = new ChannelSpec[channels.size()]; + for (int i = 0; i < channels.size(); i++) { + settings.channels[i] = new ChannelSpec(channels.get(i)); + } + + settings.periodInMs = period; + addScanRequest(settings); + + WifiScanner.WifiChangeSettings settings2 = new WifiScanner.WifiChangeSettings(); + settings2.rssiSampleSize = 3; + settings2.lostApSampleSize = 3; + settings2.unchangedSampleSize = 3; + settings2.minApsBreachingThreshold = 2; + settings2.bssidInfos = new BssidInfo[brightest.length]; + + for (int i = 0; i < brightest.length; i++) { + BssidInfo BssidInfo = new BssidInfo(); + BssidInfo.bssid = brightest[i].BSSID; + int threshold = (100 + brightest[i].level) / 32 + 2; + BssidInfo.low = brightest[i].level - threshold; + BssidInfo.high = brightest[i].level + threshold; + settings2.bssidInfos[i] = BssidInfo; + + if (DBG) localLog("Setting bssid=" + BssidInfo.bssid + ", " + + "low=" + BssidInfo.low + ", high=" + BssidInfo.high); + } + + trackSignificantWifiChange(settings2); + mCurrentBssids = brightest; + } + + private void reconfigureScan(WifiScanner.WifiChangeSettings settings) { + + if (settings.bssidInfos.length < MAX_APS_TO_TRACK) { + localLog("too few APs (" + settings.bssidInfos.length + + ") available to track wifi change"); + return; + } + + if (DBG) localLog("Setting configuration specified by app"); + + mCurrentBssids = new ScanResult[settings.bssidInfos.length]; + HashSet channels = new HashSet(); + + for (int i = 0; i < settings.bssidInfos.length; i++) { + ScanResult result = new ScanResult(); + result.BSSID = settings.bssidInfos[i].bssid; + mCurrentBssids[i] = result; + channels.add(settings.bssidInfos[i].frequencyHint); + } + + // cancel previous scan + removeScanRequest(); + + // set new scanning schedule + ScanSettings settings2 = new ScanSettings(); + settings2.band = WifiScanner.WIFI_BAND_UNSPECIFIED; + settings2.channels = new ChannelSpec[channels.size()]; + int i = 0; + for (Integer channel : channels) { + settings2.channels[i++] = new ChannelSpec(channel); + } + + settings2.periodInMs = settings.periodInMs; + addScanRequest(settings2); + + // start tracking new APs + trackSignificantWifiChange(settings); + } + + + @Override + public void onChangesFound(ScanResult results[]) { + sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results); + } + + private void addScanRequest(ScanSettings settings) { + if (DBG) localLog("Starting scans"); + if (mInternalClientInfo != null) { + mInternalClientInfo.sendRequestToClientHandler( + WifiScanner.CMD_START_BACKGROUND_SCAN, settings, null); + } + } + + private void removeScanRequest() { + if (DBG) localLog("Stopping scans"); + if (mInternalClientInfo != null) { + mInternalClientInfo.sendRequestToClientHandler( + WifiScanner.CMD_STOP_BACKGROUND_SCAN); + } + } + + private void trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings) { + mScannerImpl.untrackSignificantWifiChange(); + mScannerImpl.trackSignificantWifiChange(settings, this); + } + + private void untrackSignificantWifiChange() { + mScannerImpl.untrackSignificantWifiChange(); + } + + private void addWifiChangeHandler(ClientInfo ci, int handler) { + mActiveWifiChangeHandlers.add(Pair.create(ci, handler)); + // Add an internal client to make background scan requests. + if (mInternalClientInfo == null) { + mInternalClientInfo = + new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler())); + mInternalClientInfo.register(); + } + } + + private void removeWifiChangeHandler(ClientInfo ci, int handler) { + mActiveWifiChangeHandlers.remove(Pair.create(ci, handler)); + untrackSignificantWifiChangeOnEmpty(); + } + + private void untrackSignificantWifiChangeOnEmpty() { + if (mActiveWifiChangeHandlers.isEmpty()) { + if (DBG) localLog("Got Disable Wifi Change"); + mCurrentBssids = null; + untrackSignificantWifiChange(); + // Remove the internal client when there are no more external clients. + if (mInternalClientInfo != null) { + mInternalClientInfo.cleanup(); + mInternalClientInfo = null; + } + transitionTo(mDefaultState); + } + } + + private void reportWifiChanged(ScanResult[] results) { + WifiScanner.ParcelableScanResults parcelableScanResults = + new WifiScanner.ParcelableScanResults(results); + Iterator> it = mActiveWifiChangeHandlers.iterator(); + while (it.hasNext()) { + Pair entry = it.next(); + ClientInfo ci = entry.first; + int handler = entry.second; + ci.reportEvent(WifiScanner.CMD_WIFI_CHANGE_DETECTED, 0, handler, + parcelableScanResults); + } + } + + private void reportWifiStabilized(ScanResult[] results) { + WifiScanner.ParcelableScanResults parcelableScanResults = + new WifiScanner.ParcelableScanResults(results); + Iterator> it = mActiveWifiChangeHandlers.iterator(); + while (it.hasNext()) { + Pair entry = it.next(); + ClientInfo ci = entry.first; + int handler = entry.second; + ci.reportEvent(WifiScanner.CMD_WIFI_CHANGES_STABILIZED, 0, handler, + parcelableScanResults); + } + } + } + + private static String toString(int uid, ScanSettings settings) { + StringBuilder sb = new StringBuilder(); + sb.append("ScanSettings[uid=").append(uid); + sb.append(", period=").append(settings.periodInMs); + sb.append(", report=").append(settings.reportEvents); + if (settings.reportEvents == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL + && settings.numBssidsPerScan > 0 + && settings.maxScansToCache > 1) { + sb.append(", batch=").append(settings.maxScansToCache); + sb.append(", numAP=").append(settings.numBssidsPerScan); + } + sb.append(", ").append(ChannelHelper.toString(settings)); + sb.append("]"); + + return sb.toString(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump WifiScanner from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " without permission " + + android.Manifest.permission.DUMP); + return; + } + pw.println("WifiScanningService - Log Begin ----"); + mLocalLog.dump(fd, pw, args); + pw.println("WifiScanningService - Log End ----"); + pw.println(); + pw.println("clients:"); + for (ClientInfo client : mClients.values()) { + pw.println(" " + client); + } + pw.println("listeners:"); + for (ClientInfo client : mClients.values()) { + Collection settingsList = + mBackgroundScanStateMachine.getBackgroundScanSettings(client); + for (ScanSettings settings : settingsList) { + pw.println(" " + toString(client.mUid, settings)); + } + } + WifiNative.ScanSettings schedule = mScheduler.getSchedule(); + if (schedule != null) { + pw.println("schedule:"); + pw.println(" base period: " + schedule.base_period_ms); + pw.println(" max ap per scan: " + schedule.max_ap_per_scan); + pw.println(" batched scans: " + schedule.report_threshold_num_scans); + pw.println(" buckets:"); + for (int b = 0; b < schedule.num_buckets; b++) { + WifiNative.BucketSettings bucket = schedule.buckets[b]; + pw.println(" bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)[" + + bucket.report_events + "]: " + + ChannelHelper.toString(bucket)); + } + } + pw.println("PNO scan state machine transitions:"); + mPnoScanStateMachine.dump(fd, pw, args); + } + + void logScanRequest(String request, ClientInfo ci, int id, WorkSource workSource, + ScanSettings settings, PnoSettings pnoSettings) { + StringBuilder sb = new StringBuilder(); + sb.append(request) + .append(": ") + .append(ci.toString()) + .append(",Id=") + .append(id); + if (workSource != null) { + sb.append(",").append(workSource); + } + if (settings != null) { + sb.append(", "); + describeTo(sb, settings); + } + if (pnoSettings != null) { + sb.append(", "); + describeTo(sb, pnoSettings); + } + localLog(sb.toString()); + } + + void logCallback(String callback, ClientInfo ci, int id) { + StringBuilder sb = new StringBuilder(); + sb.append(callback) + .append(": ") + .append(ci.toString()) + .append(",Id=") + .append(id); + localLog(sb.toString()); + } + + static String describeTo(StringBuilder sb, ScanSettings scanSettings) { + sb.append("ScanSettings { ") + .append(" band:").append(scanSettings.band) + .append(" period:").append(scanSettings.periodInMs) + .append(" reportEvents:").append(scanSettings.reportEvents) + .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan) + .append(" maxScansToCache:").append(scanSettings.maxScansToCache) + .append(" channels:[ "); + if (scanSettings.channels != null) { + for (int i = 0; i < scanSettings.channels.length; i++) { + sb.append(scanSettings.channels[i].frequency) + .append(" "); + } + } + sb.append(" ] ") + .append(" } "); + return sb.toString(); + } + + static String describeTo(StringBuilder sb, PnoSettings pnoSettings) { + sb.append("PnoSettings { ") + .append(" min5GhzRssi:").append(pnoSettings.min5GHzRssi) + .append(" min24GhzRssi:").append(pnoSettings.min24GHzRssi) + .append(" initialScoreMax:").append(pnoSettings.initialScoreMax) + .append(" currentConnectionBonus:").append(pnoSettings.currentConnectionBonus) + .append(" sameNetworkBonus:").append(pnoSettings.sameNetworkBonus) + .append(" secureBonus:").append(pnoSettings.secureBonus) + .append(" band5GhzBonus:").append(pnoSettings.band5GHzBonus) + .append(" isConnected:").append(pnoSettings.isConnected) + .append(" networks:[ "); + if (pnoSettings.networkList != null) { + for (int i = 0; i < pnoSettings.networkList.length; i++) { + sb.append(pnoSettings.networkList[i].ssid) + .append(",") + .append(pnoSettings.networkList[i].networkId) + .append(" "); + } + } + sb.append(" ] ") + .append(" } "); + return sb.toString(); + } +} -- cgit v1.2.3