WificondScannerImpl.java revision 9dc9a8750ecd1ab25c5b4c7d17c8930ca2ffb6c3
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.scanner;
18
19import android.app.AlarmManager;
20import android.content.Context;
21import android.net.wifi.ScanResult;
22import android.net.wifi.WifiScanner;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.util.Log;
27
28import com.android.internal.R;
29import com.android.server.wifi.Clock;
30import com.android.server.wifi.ScanDetail;
31import com.android.server.wifi.WifiMonitor;
32import com.android.server.wifi.WifiNative;
33import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
34
35import java.util.ArrayDeque;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.Collections;
39import java.util.HashSet;
40import java.util.List;
41import java.util.Set;
42
43/**
44 * Implementation of the WifiScanner HAL API that uses wificond to perform all scans
45 * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method.
46 */
47public class WificondScannerImpl extends WifiScannerImpl implements Handler.Callback {
48    private static final String TAG = "WificondScannerImpl";
49    private static final boolean DBG = false;
50
51    public static final String BACKGROUND_PERIOD_ALARM_TAG = TAG + " Background Scan Period";
52    public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
53    // Max number of networks that can be specified to wificond per scan request
54    public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
55
56    private static final int SCAN_BUFFER_CAPACITY = 10;
57    private static final int MAX_APS_PER_SCAN = 32;
58    private static final int MAX_SCAN_BUCKETS = 16;
59
60    private final Context mContext;
61    private final WifiNative mWifiNative;
62    private final AlarmManager mAlarmManager;
63    private final Handler mEventHandler;
64    private final ChannelHelper mChannelHelper;
65    private final Clock mClock;
66
67    private final Object mSettingsLock = new Object();
68
69    // Next scan settings to apply when the previous scan completes
70    private WifiNative.ScanSettings mPendingBackgroundScanSettings = null;
71    private WifiNative.ScanEventHandler mPendingBackgroundScanEventHandler = null;
72    private WifiNative.ScanSettings mPendingSingleScanSettings = null;
73    private WifiNative.ScanEventHandler mPendingSingleScanEventHandler = null;
74
75    // Active background scan settings/state
76    private WifiNative.ScanSettings mBackgroundScanSettings = null;
77    private WifiNative.ScanEventHandler mBackgroundScanEventHandler = null;
78    private int mNextBackgroundScanPeriod = 0;
79    private int mNextBackgroundScanId = 0;
80    private boolean mBackgroundScanPeriodPending = false;
81    private boolean mBackgroundScanPaused = false;
82    private ScanBuffer mBackgroundScanBuffer = new ScanBuffer(SCAN_BUFFER_CAPACITY);
83
84    private WifiScanner.ScanData mLatestSingleScanResult =
85            new WifiScanner.ScanData(0, 0, new ScanResult[0]);
86
87    // Settings for the currently running scan, null if no scan active
88    private LastScanSettings mLastScanSettings = null;
89
90    // Pno related info.
91    private WifiNative.PnoSettings mPnoSettings = null;
92    private WifiNative.PnoEventHandler mPnoEventHandler;
93    private final boolean mHwPnoScanSupported;
94    private final HwPnoDebouncer mHwPnoDebouncer;
95    private final HwPnoDebouncer.Listener mHwPnoDebouncerListener = new HwPnoDebouncer.Listener() {
96        public void onPnoScanFailed() {
97            Log.e(TAG, "Pno scan failure received");
98            reportPnoScanFailure();
99        }
100    };
101
102    /**
103     * Duration to wait before timing out a scan.
104     *
105     * The expected behavior is that the hardware will return a failed scan if it does not
106     * complete, but timeout just in case it does not.
107     */
108    private static final long SCAN_TIMEOUT_MS = 15000;
109
110    AlarmManager.OnAlarmListener mScanPeriodListener = new AlarmManager.OnAlarmListener() {
111            public void onAlarm() {
112                synchronized (mSettingsLock) {
113                    handleScanPeriod();
114                }
115            }
116        };
117
118    AlarmManager.OnAlarmListener mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
119            public void onAlarm() {
120                synchronized (mSettingsLock) {
121                    handleScanTimeout();
122                }
123            }
124        };
125
126    public WificondScannerImpl(Context context, WifiNative wifiNative,
127                                     WifiMonitor wifiMonitor, ChannelHelper channelHelper,
128                                     Looper looper, Clock clock) {
129        mContext = context;
130        mWifiNative = wifiNative;
131        mChannelHelper = channelHelper;
132        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
133        mEventHandler = new Handler(looper, this);
134        mClock = clock;
135        mHwPnoDebouncer = new HwPnoDebouncer(mWifiNative, mAlarmManager, mEventHandler, mClock);
136
137        // Check if the device supports HW PNO scans.
138        mHwPnoScanSupported = mContext.getResources().getBoolean(
139                R.bool.config_wifi_background_scan_support);
140
141        wifiMonitor.registerHandler(mWifiNative.getInterfaceName(),
142                WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
143        wifiMonitor.registerHandler(mWifiNative.getInterfaceName(),
144                WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
145        wifiMonitor.registerHandler(mWifiNative.getInterfaceName(),
146                WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
147    }
148
149    public WificondScannerImpl(Context context, WifiNative wifiNative,
150                                     WifiMonitor wifiMonitor, Looper looper, Clock clock) {
151        // TODO get channel information from wificond.
152        this(context, wifiNative, wifiMonitor, new NoBandChannelHelper(), looper, clock);
153    }
154
155    @Override
156    public void cleanup() {
157        synchronized (mSettingsLock) {
158            mPendingSingleScanSettings = null;
159            mPendingSingleScanEventHandler = null;
160            stopHwPnoScan();
161            stopBatchedScan();
162            mLastScanSettings = null; // finally clear any active scan
163        }
164    }
165
166    @Override
167    public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) {
168        capabilities.max_scan_cache_size = Integer.MAX_VALUE;
169        capabilities.max_scan_buckets = MAX_SCAN_BUCKETS;
170        capabilities.max_ap_cache_per_scan = MAX_APS_PER_SCAN;
171        capabilities.max_rssi_sample_size = 8;
172        capabilities.max_scan_reporting_threshold = SCAN_BUFFER_CAPACITY;
173        return true;
174    }
175
176    @Override
177    public ChannelHelper getChannelHelper() {
178        return mChannelHelper;
179    }
180
181    @Override
182    public boolean startSingleScan(WifiNative.ScanSettings settings,
183            WifiNative.ScanEventHandler eventHandler) {
184        if (eventHandler == null || settings == null) {
185            Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings
186                    + ",eventHandler=" + eventHandler);
187            return false;
188        }
189        if (mPendingSingleScanSettings != null
190                || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
191            Log.w(TAG, "A single scan is already running");
192            return false;
193        }
194        synchronized (mSettingsLock) {
195            mPendingSingleScanSettings = settings;
196            mPendingSingleScanEventHandler = eventHandler;
197            processPendingScans();
198            return true;
199        }
200    }
201
202    @Override
203    public WifiScanner.ScanData getLatestSingleScanResults() {
204        return mLatestSingleScanResult;
205    }
206
207    @Override
208    public boolean startBatchedScan(WifiNative.ScanSettings settings,
209            WifiNative.ScanEventHandler eventHandler) {
210        if (settings == null || eventHandler == null) {
211            Log.w(TAG, "Invalid arguments for startBatched: settings=" + settings
212                    + ",eventHandler=" + eventHandler);
213            return false;
214        }
215
216        if (settings.max_ap_per_scan < 0 || settings.max_ap_per_scan > MAX_APS_PER_SCAN) {
217            return false;
218        }
219        if (settings.num_buckets < 0 || settings.num_buckets > MAX_SCAN_BUCKETS) {
220            return false;
221        }
222        if (settings.report_threshold_num_scans < 0
223                || settings.report_threshold_num_scans > SCAN_BUFFER_CAPACITY) {
224            return false;
225        }
226        if (settings.report_threshold_percent < 0 || settings.report_threshold_percent > 100) {
227            return false;
228        }
229        if (settings.base_period_ms <= 0) {
230            return false;
231        }
232        for (int i = 0; i < settings.num_buckets; ++i) {
233            WifiNative.BucketSettings bucket = settings.buckets[i];
234            if (bucket.period_ms % settings.base_period_ms != 0) {
235                return false;
236            }
237        }
238
239        synchronized (mSettingsLock) {
240            stopBatchedScan();
241            if (DBG) {
242                Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period="
243                        + settings.base_period_ms + " ms");
244            }
245            mPendingBackgroundScanSettings = settings;
246            mPendingBackgroundScanEventHandler = eventHandler;
247            handleScanPeriod(); // Try to start scan immediately
248            return true;
249        }
250    }
251
252    @Override
253    public void stopBatchedScan() {
254        synchronized (mSettingsLock) {
255            if (DBG) Log.d(TAG, "Stopping scan");
256            mBackgroundScanSettings = null;
257            mBackgroundScanEventHandler = null;
258            mPendingBackgroundScanSettings = null;
259            mPendingBackgroundScanEventHandler = null;
260            mBackgroundScanPaused = false;
261            mBackgroundScanPeriodPending = false;
262            unscheduleScansLocked();
263        }
264        processPendingScans();
265    }
266
267    @Override
268    public void pauseBatchedScan() {
269        synchronized (mSettingsLock) {
270            if (DBG) Log.d(TAG, "Pausing scan");
271            // if there isn't a pending scan then make the current scan pending
272            if (mPendingBackgroundScanSettings == null) {
273                mPendingBackgroundScanSettings = mBackgroundScanSettings;
274                mPendingBackgroundScanEventHandler = mBackgroundScanEventHandler;
275            }
276            mBackgroundScanSettings = null;
277            mBackgroundScanEventHandler = null;
278            mBackgroundScanPeriodPending = false;
279            mBackgroundScanPaused = true;
280
281            unscheduleScansLocked();
282
283            WifiScanner.ScanData[] results = getLatestBatchedScanResults(/* flush = */ true);
284            if (mPendingBackgroundScanEventHandler != null) {
285                mPendingBackgroundScanEventHandler.onScanPaused(results);
286            }
287        }
288        processPendingScans();
289    }
290
291    @Override
292    public void restartBatchedScan() {
293        synchronized (mSettingsLock) {
294            if (DBG) Log.d(TAG, "Restarting scan");
295            if (mPendingBackgroundScanEventHandler != null) {
296                mPendingBackgroundScanEventHandler.onScanRestarted();
297            }
298            mBackgroundScanPaused = false;
299            handleScanPeriod();
300        }
301    }
302
303    private void unscheduleScansLocked() {
304        mAlarmManager.cancel(mScanPeriodListener);
305        if (mLastScanSettings != null) {
306            mLastScanSettings.backgroundScanActive = false;
307        }
308    }
309
310    private void handleScanPeriod() {
311        synchronized (mSettingsLock) {
312            mBackgroundScanPeriodPending = true;
313            processPendingScans();
314        }
315    }
316
317    private void handleScanTimeout() {
318        Log.e(TAG, "Timed out waiting for scan result from wificond");
319        reportScanFailure();
320        processPendingScans();
321    }
322
323    private boolean isDifferentPnoScanSettings(LastScanSettings newScanSettings) {
324        return (mLastScanSettings == null || !Arrays.equals(
325                newScanSettings.pnoNetworkList, mLastScanSettings.pnoNetworkList));
326    }
327
328    private void processPendingScans() {
329        synchronized (mSettingsLock) {
330            // Wait for the active scan result to come back to reschedule other scans,
331            // unless if HW pno scan is running. Hw PNO scans are paused it if there
332            // are other pending scans,
333            if (mLastScanSettings != null && !mLastScanSettings.hwPnoScanActive) {
334                return;
335            }
336
337            ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
338            Set<String> hiddenNetworkSSIDSet = new HashSet<>();
339            final LastScanSettings newScanSettings =
340                    new LastScanSettings(mClock.getElapsedSinceBootMillis());
341
342            // Update scan settings if there is a pending scan
343            if (!mBackgroundScanPaused) {
344                if (mPendingBackgroundScanSettings != null) {
345                    mBackgroundScanSettings = mPendingBackgroundScanSettings;
346                    mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
347                    mNextBackgroundScanPeriod = 0;
348                    mPendingBackgroundScanSettings = null;
349                    mPendingBackgroundScanEventHandler = null;
350                    mBackgroundScanPeriodPending = true;
351                }
352                if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
353                    int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
354                    for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
355                            ++bucket_id) {
356                        WifiNative.BucketSettings bucket =
357                                mBackgroundScanSettings.buckets[bucket_id];
358                        if (mNextBackgroundScanPeriod % (bucket.period_ms
359                                        / mBackgroundScanSettings.base_period_ms) == 0) {
360                            if ((bucket.report_events
361                                            & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
362                                reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
363                            }
364                            if ((bucket.report_events
365                                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
366                                reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
367                            }
368                            // only no batch if all buckets specify it
369                            if ((bucket.report_events
370                                            & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
371                                reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
372                            }
373
374                            allFreqs.addChannels(bucket);
375                        }
376                    }
377                    if (!allFreqs.isEmpty()) {
378                        newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
379                                mBackgroundScanSettings.max_ap_per_scan, reportEvents,
380                                mBackgroundScanSettings.report_threshold_num_scans,
381                                mBackgroundScanSettings.report_threshold_percent);
382                    }
383                    mNextBackgroundScanPeriod++;
384                    mBackgroundScanPeriodPending = false;
385                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
386                            mClock.getElapsedSinceBootMillis()
387                                    + mBackgroundScanSettings.base_period_ms,
388                            BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
389                }
390            }
391
392            if (mPendingSingleScanSettings != null) {
393                boolean reportFullResults = false;
394                ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
395                for (int i = 0; i < mPendingSingleScanSettings.num_buckets; ++i) {
396                    WifiNative.BucketSettings bucketSettings =
397                            mPendingSingleScanSettings.buckets[i];
398                    if ((bucketSettings.report_events
399                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
400                        reportFullResults = true;
401                    }
402                    singleScanFreqs.addChannels(bucketSettings);
403                    allFreqs.addChannels(bucketSettings);
404                }
405                newScanSettings.setSingleScan(reportFullResults, singleScanFreqs,
406                        mPendingSingleScanEventHandler);
407
408                WifiNative.HiddenNetwork[] hiddenNetworks =
409                        mPendingSingleScanSettings.hiddenNetworks;
410                if (hiddenNetworks != null) {
411                    int numHiddenNetworks =
412                            Math.min(hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
413                    for (int i = 0; i < numHiddenNetworks; i++) {
414                        hiddenNetworkSSIDSet.add(hiddenNetworks[i].ssid);
415                    }
416                }
417
418                mPendingSingleScanSettings = null;
419                mPendingSingleScanEventHandler = null;
420            }
421
422            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
423                    && !allFreqs.isEmpty()) {
424                pauseHwPnoScan();
425                Set<Integer> freqs = allFreqs.getScanFreqs();
426                boolean success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
427                if (success) {
428                    // TODO handle scan timeout
429                    if (DBG) {
430                        Log.d(TAG, "Starting wifi scan for freqs=" + freqs
431                                + ", background=" + newScanSettings.backgroundScanActive
432                                + ", single=" + newScanSettings.singleScanActive);
433                    }
434                    mLastScanSettings = newScanSettings;
435                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
436                            mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
437                            TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
438                } else {
439                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
440                    // indicate scan failure async
441                    mEventHandler.post(new Runnable() {
442                            public void run() {
443                                if (newScanSettings.singleScanEventHandler != null) {
444                                    newScanSettings.singleScanEventHandler
445                                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
446                                }
447                            }
448                        });
449                    // TODO(b/27769665) background scans should be failed too if scans fail enough
450                }
451            } else if (isHwPnoScanRequired()) {
452                newScanSettings.setHwPnoScan(mPnoSettings.networkList, mPnoEventHandler);
453                boolean status;
454                // If the PNO network list has changed from the previous request, ensure that
455                // we bypass the debounce logic and restart PNO scan.
456                if (isDifferentPnoScanSettings(newScanSettings)) {
457                    status = restartHwPnoScan(mPnoSettings);
458                } else {
459                    status = startHwPnoScan(mPnoSettings);
460                }
461                if (status) {
462                    mLastScanSettings = newScanSettings;
463                } else {
464                    Log.e(TAG, "Failed to start PNO scan");
465                    // indicate scan failure async
466                    mEventHandler.post(new Runnable() {
467                        public void run() {
468                            if (mPnoEventHandler != null) {
469                                mPnoEventHandler.onPnoScanFailed();
470                            }
471                            // Clean up PNO state, we don't want to continue PNO scanning.
472                            mPnoSettings = null;
473                            mPnoEventHandler = null;
474                        }
475                    });
476                }
477            }
478        }
479    }
480
481    @Override
482    public boolean handleMessage(Message msg) {
483        switch(msg.what) {
484            case WifiMonitor.SCAN_FAILED_EVENT:
485                Log.w(TAG, "Scan failed");
486                mAlarmManager.cancel(mScanTimeoutListener);
487                reportScanFailure();
488                processPendingScans();
489                break;
490            case WifiMonitor.PNO_SCAN_RESULTS_EVENT:
491                pollLatestScanDataForPno();
492                processPendingScans();
493                break;
494            case WifiMonitor.SCAN_RESULTS_EVENT:
495                mAlarmManager.cancel(mScanTimeoutListener);
496                pollLatestScanData();
497                processPendingScans();
498                break;
499            default:
500                // ignore unknown event
501        }
502        return true;
503    }
504
505    private void reportScanFailure() {
506        synchronized (mSettingsLock) {
507            if (mLastScanSettings != null) {
508                if (mLastScanSettings.singleScanEventHandler != null) {
509                    mLastScanSettings.singleScanEventHandler
510                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
511                }
512                // TODO(b/27769665) background scans should be failed too if scans fail enough
513                mLastScanSettings = null;
514            }
515        }
516    }
517
518    private void reportPnoScanFailure() {
519        synchronized (mSettingsLock) {
520            if (mLastScanSettings != null && mLastScanSettings.hwPnoScanActive) {
521                if (mLastScanSettings.pnoScanEventHandler != null) {
522                    mLastScanSettings.pnoScanEventHandler.onPnoScanFailed();
523                }
524                // Clean up PNO state, we don't want to continue PNO scanning.
525                mPnoSettings = null;
526                mPnoEventHandler = null;
527                mLastScanSettings = null;
528            }
529        }
530    }
531
532    private void pollLatestScanDataForPno() {
533        synchronized (mSettingsLock) {
534            if (mLastScanSettings == null) {
535                 // got a scan before we started scanning or after scan was canceled
536                return;
537            }
538            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
539            List<ScanResult> hwPnoScanResults = new ArrayList<>();
540            int numFilteredScanResults = 0;
541            for (int i = 0; i < nativeResults.size(); ++i) {
542                ScanResult result = nativeResults.get(i).getScanResult();
543                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
544                if (timestamp_ms > mLastScanSettings.startTime) {
545                    if (mLastScanSettings.hwPnoScanActive) {
546                        hwPnoScanResults.add(result);
547                    }
548                } else {
549                    numFilteredScanResults++;
550                }
551            }
552
553            if (numFilteredScanResults != 0) {
554                Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results.");
555            }
556
557            if (mLastScanSettings.hwPnoScanActive
558                    && mLastScanSettings.pnoScanEventHandler != null) {
559                ScanResult[] pnoScanResultsArray = new ScanResult[hwPnoScanResults.size()];
560                for (int i = 0; i < pnoScanResultsArray.length; ++i) {
561                    ScanResult result = nativeResults.get(i).getScanResult();
562                    pnoScanResultsArray[i] = hwPnoScanResults.get(i);
563                }
564                mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
565            }
566            // mLastScanSettings is for either single/batched scan or pno scan.
567            // We can safely set it to null when pno scan finishes.
568            mLastScanSettings = null;
569        }
570    }
571
572    private void pollLatestScanData() {
573        synchronized (mSettingsLock) {
574            if (mLastScanSettings == null) {
575                 // got a scan before we started scanning or after scan was canceled
576                return;
577            }
578
579            if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
580            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
581            List<ScanResult> singleScanResults = new ArrayList<>();
582            List<ScanResult> backgroundScanResults = new ArrayList<>();
583            int numFilteredScanResults = 0;
584            for (int i = 0; i < nativeResults.size(); ++i) {
585                ScanResult result = nativeResults.get(i).getScanResult();
586                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
587                if (timestamp_ms > mLastScanSettings.startTime) {
588                    if (mLastScanSettings.backgroundScanActive) {
589                        backgroundScanResults.add(result);
590                    }
591                    if (mLastScanSettings.singleScanActive
592                            && mLastScanSettings.singleScanFreqs.containsChannel(
593                                    result.frequency)) {
594                        singleScanResults.add(result);
595                    }
596                } else {
597                    numFilteredScanResults++;
598                }
599            }
600            if (numFilteredScanResults != 0) {
601                Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
602            }
603
604            if (mLastScanSettings.backgroundScanActive) {
605                if (mBackgroundScanEventHandler != null) {
606                    if ((mLastScanSettings.reportEvents
607                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
608                        for (ScanResult scanResult : backgroundScanResults) {
609                            // TODO(b/27506257): Fill in correct bucketsScanned value
610                            mBackgroundScanEventHandler.onFullScanResult(scanResult, 0);
611                        }
612                    }
613                }
614
615                Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR);
616                ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps,
617                            backgroundScanResults.size())];
618                for (int i = 0; i < scanResultsArray.length; ++i) {
619                    scanResultsArray[i] = backgroundScanResults.get(i);
620                }
621
622                if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
623                    // TODO(b/27506257): Fill in correct bucketsScanned value
624                    mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0,
625                                    scanResultsArray));
626                }
627
628                if (mBackgroundScanEventHandler != null) {
629                    if ((mLastScanSettings.reportEvents
630                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
631                            || (mLastScanSettings.reportEvents
632                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0
633                            || (mLastScanSettings.reportEvents
634                                    == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
635                                    && (mBackgroundScanBuffer.size()
636                                            >= (mBackgroundScanBuffer.capacity()
637                                                    * mLastScanSettings.reportPercentThreshold
638                                                    / 100)
639                                            || mBackgroundScanBuffer.size()
640                                            >= mLastScanSettings.reportNumScansThreshold))) {
641                        mBackgroundScanEventHandler
642                                .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
643                    }
644                }
645            }
646
647            if (mLastScanSettings.singleScanActive
648                    && mLastScanSettings.singleScanEventHandler != null) {
649                if (mLastScanSettings.reportSingleScanFullResults) {
650                    for (ScanResult scanResult : singleScanResults) {
651                        // ignore buckets scanned since there is only one bucket for a single scan
652                        mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult,
653                                /* bucketsScanned */ 0);
654                    }
655                }
656                Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
657                mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
658                        mLastScanSettings.singleScanFreqs.isAllChannels(),
659                        singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
660                mLastScanSettings.singleScanEventHandler
661                        .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
662            }
663
664            mLastScanSettings = null;
665        }
666    }
667
668
669    @Override
670    public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
671        synchronized (mSettingsLock) {
672            WifiScanner.ScanData[] results = mBackgroundScanBuffer.get();
673            if (flush) {
674                mBackgroundScanBuffer.clear();
675            }
676            return results;
677        }
678    }
679
680    private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
681        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
682    }
683
684    private void stopHwPnoScan() {
685        mHwPnoDebouncer.stopPnoScan();
686    }
687
688    private void pauseHwPnoScan() {
689        mHwPnoDebouncer.forceStopPnoScan();
690    }
691
692    private boolean restartHwPnoScan(WifiNative.PnoSettings pnoSettings) {
693        mHwPnoDebouncer.forceStopPnoScan();
694        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
695    }
696
697    /**
698     * Hw Pno Scan is required only for disconnected PNO when the device supports it.
699     * @param isConnectedPno Whether this is connected PNO vs disconnected PNO.
700     * @return true if HW PNO scan is required, false otherwise.
701     */
702    private boolean isHwPnoScanRequired(boolean isConnectedPno) {
703        return (!isConnectedPno & mHwPnoScanSupported);
704    }
705
706    private boolean isHwPnoScanRequired() {
707        if (mPnoSettings == null) return false;
708        return isHwPnoScanRequired(mPnoSettings.isConnected);
709    }
710
711    @Override
712    public boolean setHwPnoList(WifiNative.PnoSettings settings,
713            WifiNative.PnoEventHandler eventHandler) {
714        synchronized (mSettingsLock) {
715            if (mPnoSettings != null) {
716                Log.w(TAG, "Already running a PNO scan");
717                return false;
718            }
719            mPnoEventHandler = eventHandler;
720            mPnoSettings = settings;
721
722            // For wificond based PNO, we start the scan immediately when we set pno list.
723            processPendingScans();
724            return true;
725        }
726    }
727
728    @Override
729    public boolean resetHwPnoList() {
730        synchronized (mSettingsLock) {
731            if (mPnoSettings == null) {
732                Log.w(TAG, "No PNO scan running");
733                return false;
734            }
735            mPnoEventHandler = null;
736            mPnoSettings = null;
737            // For wificond based PNO, we stop the scan immediately when we reset pno list.
738            stopHwPnoScan();
739            return true;
740        }
741    }
742
743    @Override
744    public boolean isHwPnoSupported(boolean isConnectedPno) {
745        // Hw Pno Scan is supported only for disconnected PNO when the device supports it.
746        return isHwPnoScanRequired(isConnectedPno);
747    }
748
749    @Override
750    public boolean shouldScheduleBackgroundScanForHwPno() {
751        return false;
752    }
753
754    private static class LastScanSettings {
755        public long startTime;
756
757        LastScanSettings(long startTime) {
758            this.startTime = startTime;
759        }
760
761        // Background settings
762        public boolean backgroundScanActive = false;
763        public int scanId;
764        public int maxAps;
765        public int reportEvents;
766        public int reportNumScansThreshold;
767        public int reportPercentThreshold;
768
769        public void setBackgroundScan(int scanId, int maxAps, int reportEvents,
770                int reportNumScansThreshold, int reportPercentThreshold) {
771            this.backgroundScanActive = true;
772            this.scanId = scanId;
773            this.maxAps = maxAps;
774            this.reportEvents = reportEvents;
775            this.reportNumScansThreshold = reportNumScansThreshold;
776            this.reportPercentThreshold = reportPercentThreshold;
777        }
778
779        // Single scan settings
780        public boolean singleScanActive = false;
781        public boolean reportSingleScanFullResults;
782        public ChannelCollection singleScanFreqs;
783        public WifiNative.ScanEventHandler singleScanEventHandler;
784
785        public void setSingleScan(boolean reportSingleScanFullResults,
786                ChannelCollection singleScanFreqs,
787                WifiNative.ScanEventHandler singleScanEventHandler) {
788            singleScanActive = true;
789            this.reportSingleScanFullResults = reportSingleScanFullResults;
790            this.singleScanFreqs = singleScanFreqs;
791            this.singleScanEventHandler = singleScanEventHandler;
792        }
793
794        public boolean hwPnoScanActive = false;
795        public WifiNative.PnoNetwork[] pnoNetworkList;
796        public WifiNative.PnoEventHandler pnoScanEventHandler;
797
798        public void setHwPnoScan(
799                WifiNative.PnoNetwork[] pnoNetworkList,
800                WifiNative.PnoEventHandler pnoScanEventHandler) {
801            hwPnoScanActive = true;
802            this.pnoNetworkList = pnoNetworkList;
803            this.pnoScanEventHandler = pnoScanEventHandler;
804        }
805    }
806
807
808    private static class ScanBuffer {
809        private final ArrayDeque<WifiScanner.ScanData> mBuffer;
810        private int mCapacity;
811
812        ScanBuffer(int capacity) {
813            mCapacity = capacity;
814            mBuffer = new ArrayDeque<>(mCapacity);
815        }
816
817        public int size() {
818            return mBuffer.size();
819        }
820
821        public int capacity() {
822            return mCapacity;
823        }
824
825        public boolean isFull() {
826            return size() == mCapacity;
827        }
828
829        public void add(WifiScanner.ScanData scanData) {
830            if (isFull()) {
831                mBuffer.pollFirst();
832            }
833            mBuffer.offerLast(scanData);
834        }
835
836        public void clear() {
837            mBuffer.clear();
838        }
839
840        public WifiScanner.ScanData[] get() {
841            return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]);
842        }
843    }
844
845    /**
846     * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
847     * state too often which is not handled very well by some drivers.
848     * Note: This is not thread safe!
849     */
850    public static class HwPnoDebouncer {
851        public static final String PNO_DEBOUNCER_ALARM_TAG = TAG + "Pno Monitor";
852        private static final int MINIMUM_PNO_GAP_MS = 5 * 1000;
853
854        private final WifiNative mWifiNative;
855        private final AlarmManager mAlarmManager;
856        private final Handler mEventHandler;
857        private final Clock mClock;
858        private long mLastPnoChangeTimeStamp = -1L;
859        private boolean mExpectedPnoState = false;
860        private boolean mCurrentPnoState = false;;
861        private boolean mWaitForTimer = false;
862        private Listener mListener;
863        private WifiNative.PnoSettings mPnoSettings;
864
865        /**
866         * Interface used to indicate PNO scan notifications.
867         */
868        public interface Listener {
869            /**
870             * Used to indicate a delayed PNO scan request failure.
871             */
872            void onPnoScanFailed();
873        }
874
875        public HwPnoDebouncer(WifiNative wifiNative, AlarmManager alarmManager,
876                Handler eventHandler, Clock clock) {
877            mWifiNative = wifiNative;
878            mAlarmManager = alarmManager;
879            mEventHandler = eventHandler;
880            mClock = clock;
881        }
882
883        /**
884         * Enable PNO state in wificond
885         */
886        private boolean startPnoScanInternal() {
887            if (mCurrentPnoState) {
888                if (DBG) Log.d(TAG, "PNO state is already enable");
889                return true;
890            }
891            if (mPnoSettings == null) {
892                Log.e(TAG, "PNO state change to enable failed, no available Pno settings");
893                return false;
894            }
895            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
896            Log.d(TAG, "Remove all networks from supplicant before starting PNO scan");
897            mWifiNative.removeAllNetworks();
898            if (mWifiNative.startPnoScan(mPnoSettings)) {
899                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to enable");
900                mCurrentPnoState = true;
901                return true;
902            } else {
903                Log.e(TAG, "PNO state change to enable failed");
904                mCurrentPnoState = false;
905            }
906            return false;
907        }
908
909        /**
910         * Disable PNO state in wificond
911         */
912        private boolean stopPnoScanInternal() {
913            if (!mCurrentPnoState) {
914                if (DBG) Log.d(TAG, "PNO state is already disable");
915                return true;
916            }
917            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
918            if (mWifiNative.stopPnoScan()) {
919                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to disable");
920                mCurrentPnoState = false;
921                return true;
922            } else {
923                Log.e(TAG, "PNO state change to disable failed");
924                mCurrentPnoState = false;
925            }
926            return false;
927        }
928
929        private final AlarmManager.OnAlarmListener mAlarmListener =
930                new AlarmManager.OnAlarmListener() {
931            public void onAlarm() {
932                if (DBG) Log.d(TAG, "PNO timer expired, expected state " + mExpectedPnoState);
933                if (mExpectedPnoState) {
934                    if (!startPnoScanInternal()) {
935                        if (mListener != null) {
936                            mListener.onPnoScanFailed();
937                        }
938                    }
939                } else {
940                    stopPnoScanInternal();
941                }
942                mWaitForTimer = false;
943            }
944        };
945
946        /**
947         * Enable/Disable PNO state. This method will debounce PNO scan requests.
948         * @param enable boolean indicating whether PNO is being enabled or disabled.
949         */
950        private boolean setPnoState(boolean enable) {
951            boolean isSuccess = true;
952            mExpectedPnoState = enable;
953            if (!mWaitForTimer) {
954                long timeDifference = mClock.getElapsedSinceBootMillis() - mLastPnoChangeTimeStamp;
955                if (timeDifference >= MINIMUM_PNO_GAP_MS) {
956                    if (enable) {
957                        isSuccess = startPnoScanInternal();
958                    } else {
959                        isSuccess = stopPnoScanInternal();
960                    }
961                } else {
962                    long alarmTimeout = MINIMUM_PNO_GAP_MS - timeDifference;
963                    Log.d(TAG, "Start PNO timer with delay " + alarmTimeout);
964                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
965                            mClock.getElapsedSinceBootMillis() + alarmTimeout,
966                            PNO_DEBOUNCER_ALARM_TAG,
967                            mAlarmListener, mEventHandler);
968                    mWaitForTimer = true;
969                }
970            }
971            return isSuccess;
972        }
973
974        /**
975         * Start PNO scan
976         */
977        public boolean startPnoScan(WifiNative.PnoSettings pnoSettings, Listener listener) {
978            if (DBG) Log.d(TAG, "Starting PNO scan");
979            mListener = listener;
980            mPnoSettings = pnoSettings;
981            if (!setPnoState(true)) {
982                mListener = null;
983                return false;
984            }
985            return true;
986        }
987
988        /**
989         * Stop PNO scan
990         */
991        public void stopPnoScan() {
992            if (DBG) Log.d(TAG, "Stopping PNO scan");
993            setPnoState(false);
994            mListener = null;
995        }
996
997        /**
998         * Force stop PNO scanning. This method will bypass the debounce logic and stop PNO
999         * scan immediately.
1000         */
1001        public void forceStopPnoScan() {
1002            if (DBG) Log.d(TAG, "Force stopping Pno scan");
1003            // Cancel the debounce timer and stop PNO scan.
1004            if (mWaitForTimer) {
1005                mAlarmManager.cancel(mAlarmListener);
1006                mWaitForTimer = false;
1007            }
1008            stopPnoScanInternal();
1009        }
1010    }
1011}
1012