WificondScannerImpl.java revision 8149a50728c1394e11a4441a1d76eb6d965a9097
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 figure out how to get channel information from supplicant
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            // TODO(b/36276738): Remove this after we fix b/36231150.
198            Log.d(TAG, "processPendingScans in request of startSingleScan");
199            processPendingScans();
200            return true;
201        }
202    }
203
204    @Override
205    public WifiScanner.ScanData getLatestSingleScanResults() {
206        return mLatestSingleScanResult;
207    }
208
209    @Override
210    public boolean startBatchedScan(WifiNative.ScanSettings settings,
211            WifiNative.ScanEventHandler eventHandler) {
212        if (settings == null || eventHandler == null) {
213            Log.w(TAG, "Invalid arguments for startBatched: settings=" + settings
214                    + ",eventHandler=" + eventHandler);
215            return false;
216        }
217
218        if (settings.max_ap_per_scan < 0 || settings.max_ap_per_scan > MAX_APS_PER_SCAN) {
219            return false;
220        }
221        if (settings.num_buckets < 0 || settings.num_buckets > MAX_SCAN_BUCKETS) {
222            return false;
223        }
224        if (settings.report_threshold_num_scans < 0
225                || settings.report_threshold_num_scans > SCAN_BUFFER_CAPACITY) {
226            return false;
227        }
228        if (settings.report_threshold_percent < 0 || settings.report_threshold_percent > 100) {
229            return false;
230        }
231        if (settings.base_period_ms <= 0) {
232            return false;
233        }
234        for (int i = 0; i < settings.num_buckets; ++i) {
235            WifiNative.BucketSettings bucket = settings.buckets[i];
236            if (bucket.period_ms % settings.base_period_ms != 0) {
237                return false;
238            }
239        }
240
241        synchronized (mSettingsLock) {
242            stopBatchedScan();
243            if (DBG) {
244                Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period="
245                        + settings.base_period_ms + " ms");
246            }
247            mPendingBackgroundScanSettings = settings;
248            mPendingBackgroundScanEventHandler = eventHandler;
249            handleScanPeriod(); // Try to start scan immediately
250            return true;
251        }
252    }
253
254    @Override
255    public void stopBatchedScan() {
256        synchronized (mSettingsLock) {
257            if (DBG) Log.d(TAG, "Stopping scan");
258            mBackgroundScanSettings = null;
259            mBackgroundScanEventHandler = null;
260            mPendingBackgroundScanSettings = null;
261            mPendingBackgroundScanEventHandler = null;
262            mBackgroundScanPaused = false;
263            mBackgroundScanPeriodPending = false;
264            unscheduleScansLocked();
265        }
266        // TODO(b/36276738): Remove this after we fix b/36231150.
267        Log.d(TAG, "processPendingScans in request of stopBatchedScan");
268        processPendingScans();
269    }
270
271    @Override
272    public void pauseBatchedScan() {
273        synchronized (mSettingsLock) {
274            if (DBG) Log.d(TAG, "Pausing scan");
275            // if there isn't a pending scan then make the current scan pending
276            if (mPendingBackgroundScanSettings == null) {
277                mPendingBackgroundScanSettings = mBackgroundScanSettings;
278                mPendingBackgroundScanEventHandler = mBackgroundScanEventHandler;
279            }
280            mBackgroundScanSettings = null;
281            mBackgroundScanEventHandler = null;
282            mBackgroundScanPeriodPending = false;
283            mBackgroundScanPaused = true;
284
285            unscheduleScansLocked();
286
287            WifiScanner.ScanData[] results = getLatestBatchedScanResults(/* flush = */ true);
288            if (mPendingBackgroundScanEventHandler != null) {
289                mPendingBackgroundScanEventHandler.onScanPaused(results);
290            }
291        }
292        // TODO(b/36276738): Remove this after we fix b/36231150.
293        Log.d(TAG, "processPendingScans in request of pauseBatchedScan");
294        processPendingScans();
295    }
296
297    @Override
298    public void restartBatchedScan() {
299        synchronized (mSettingsLock) {
300            if (DBG) Log.d(TAG, "Restarting scan");
301            if (mPendingBackgroundScanEventHandler != null) {
302                mPendingBackgroundScanEventHandler.onScanRestarted();
303            }
304            mBackgroundScanPaused = false;
305            handleScanPeriod();
306        }
307    }
308
309    private void unscheduleScansLocked() {
310        mAlarmManager.cancel(mScanPeriodListener);
311        if (mLastScanSettings != null) {
312            mLastScanSettings.backgroundScanActive = false;
313        }
314    }
315
316    private void handleScanPeriod() {
317        synchronized (mSettingsLock) {
318            mBackgroundScanPeriodPending = true;
319            // TODO(b/36276738): Remove this after we fix b/36231150.
320            Log.d(TAG, "processPendingScans in request of handleScanPeriod");
321            processPendingScans();
322        }
323    }
324
325    private void handleScanTimeout() {
326        Log.e(TAG, "Timed out waiting for scan result from wificond");
327        reportScanFailure();
328        // TODO(b/36276738): Remove this after we fix b/36231150.
329        Log.d(TAG, "processPendingScans in request of handleScanTimeout");
330        processPendingScans();
331    }
332
333    private boolean isDifferentPnoScanSettings(LastScanSettings newScanSettings) {
334        return (mLastScanSettings == null || !Arrays.equals(
335                newScanSettings.pnoNetworkList, mLastScanSettings.pnoNetworkList));
336    }
337
338    private void processPendingScans() {
339        synchronized (mSettingsLock) {
340            // Wait for the active scan result to come back to reschedule other scans,
341            // unless if HW pno scan is running. Hw PNO scans are paused it if there
342            // are other pending scans,
343            if (mLastScanSettings != null && !mLastScanSettings.hwPnoScanActive) {
344                return;
345            }
346
347            ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
348            Set<String> hiddenNetworkSSIDSet = new HashSet<>();
349            final LastScanSettings newScanSettings =
350                    new LastScanSettings(mClock.getElapsedSinceBootMillis());
351
352            // Update scan settings if there is a pending scan
353            if (!mBackgroundScanPaused) {
354                if (mPendingBackgroundScanSettings != null) {
355                    mBackgroundScanSettings = mPendingBackgroundScanSettings;
356                    mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
357                    mNextBackgroundScanPeriod = 0;
358                    mPendingBackgroundScanSettings = null;
359                    mPendingBackgroundScanEventHandler = null;
360                    mBackgroundScanPeriodPending = true;
361                }
362                if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
363                    int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
364                    for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
365                            ++bucket_id) {
366                        WifiNative.BucketSettings bucket =
367                                mBackgroundScanSettings.buckets[bucket_id];
368                        if (mNextBackgroundScanPeriod % (bucket.period_ms
369                                        / mBackgroundScanSettings.base_period_ms) == 0) {
370                            if ((bucket.report_events
371                                            & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
372                                reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
373                            }
374                            if ((bucket.report_events
375                                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
376                                reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
377                            }
378                            // only no batch if all buckets specify it
379                            if ((bucket.report_events
380                                            & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
381                                reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
382                            }
383
384                            allFreqs.addChannels(bucket);
385                        }
386                    }
387                    if (!allFreqs.isEmpty()) {
388                        newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
389                                mBackgroundScanSettings.max_ap_per_scan, reportEvents,
390                                mBackgroundScanSettings.report_threshold_num_scans,
391                                mBackgroundScanSettings.report_threshold_percent);
392                    }
393                    mNextBackgroundScanPeriod++;
394                    mBackgroundScanPeriodPending = false;
395                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
396                            mClock.getElapsedSinceBootMillis()
397                                    + mBackgroundScanSettings.base_period_ms,
398                            BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
399                }
400            }
401
402            if (mPendingSingleScanSettings != null) {
403                boolean reportFullResults = false;
404                ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
405                for (int i = 0; i < mPendingSingleScanSettings.num_buckets; ++i) {
406                    WifiNative.BucketSettings bucketSettings =
407                            mPendingSingleScanSettings.buckets[i];
408                    if ((bucketSettings.report_events
409                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
410                        reportFullResults = true;
411                    }
412                    singleScanFreqs.addChannels(bucketSettings);
413                    allFreqs.addChannels(bucketSettings);
414                }
415                newScanSettings.setSingleScan(reportFullResults, singleScanFreqs,
416                        mPendingSingleScanEventHandler);
417
418                WifiNative.HiddenNetwork[] hiddenNetworks =
419                        mPendingSingleScanSettings.hiddenNetworks;
420                if (hiddenNetworks != null) {
421                    int numHiddenNetworks =
422                            Math.min(hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
423                    for (int i = 0; i < numHiddenNetworks; i++) {
424                        hiddenNetworkSSIDSet.add(hiddenNetworks[i].ssid);
425                    }
426                }
427
428                mPendingSingleScanSettings = null;
429                mPendingSingleScanEventHandler = null;
430            }
431
432            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
433                    && !allFreqs.isEmpty()) {
434                pauseHwPnoScan();
435                Set<Integer> freqs = allFreqs.getSupplicantScanFreqs();
436                boolean success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
437                if (success) {
438                    // TODO handle scan timeout
439                    // TODO(b/36276738): Change this to DBG log after we fix b/36231150.
440                    Log.d(TAG, "Starting wifi scan for freqs=" + freqs
441                            + ", background=" + newScanSettings.backgroundScanActive
442                            + ", single=" + newScanSettings.singleScanActive);
443                    mLastScanSettings = newScanSettings;
444                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
445                            mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
446                            TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
447                } else {
448                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
449                    // indicate scan failure async
450                    mEventHandler.post(new Runnable() {
451                            public void run() {
452                                if (newScanSettings.singleScanEventHandler != null) {
453                                    newScanSettings.singleScanEventHandler
454                                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
455                                }
456                            }
457                        });
458                    // TODO(b/27769665) background scans should be failed too if scans fail enough
459                }
460            } else if (isHwPnoScanRequired()) {
461                newScanSettings.setHwPnoScan(mPnoSettings.networkList, mPnoEventHandler);
462                boolean status;
463                // If the PNO network list has changed from the previous request, ensure that
464                // we bypass the debounce logic and restart PNO scan.
465                if (isDifferentPnoScanSettings(newScanSettings)) {
466                    status = restartHwPnoScan(mPnoSettings);
467                } else {
468                    status = startHwPnoScan(mPnoSettings);
469                }
470                if (status) {
471                    mLastScanSettings = newScanSettings;
472                } else {
473                    Log.e(TAG, "Failed to start PNO scan");
474                    // indicate scan failure async
475                    mEventHandler.post(new Runnable() {
476                        public void run() {
477                            if (mPnoEventHandler != null) {
478                                mPnoEventHandler.onPnoScanFailed();
479                            }
480                            // Clean up PNO state, we don't want to continue PNO scanning.
481                            mPnoSettings = null;
482                            mPnoEventHandler = null;
483                        }
484                    });
485                }
486            }
487        }
488    }
489
490    @Override
491    public boolean handleMessage(Message msg) {
492        switch(msg.what) {
493            case WifiMonitor.SCAN_FAILED_EVENT:
494                Log.w(TAG, "Scan failed");
495                mAlarmManager.cancel(mScanTimeoutListener);
496                reportScanFailure();
497                processPendingScans();
498                break;
499            case WifiMonitor.PNO_SCAN_RESULTS_EVENT:
500                mAlarmManager.cancel(mScanTimeoutListener);
501                pollLatestScanDataForPno();
502                // TODO(b/36276738): Remove this after we fix b/36231150.
503                Log.d(TAG, "processPendingScans in request of SCHED_SCAN_RESULTS_EVENT");
504                processPendingScans();
505                break;
506            case WifiMonitor.SCAN_RESULTS_EVENT:
507                mAlarmManager.cancel(mScanTimeoutListener);
508                pollLatestScanData();
509                // TODO(b/36276738): Remove this after we fix b/36231150.
510                Log.d(TAG, "processPendingScans in request of SCAN_RESULTS_EVENT");
511                processPendingScans();
512                break;
513            default:
514                // ignore unknown event
515        }
516        return true;
517    }
518
519    private void reportScanFailure() {
520        synchronized (mSettingsLock) {
521            if (mLastScanSettings != null) {
522                if (mLastScanSettings.singleScanEventHandler != null) {
523                    mLastScanSettings.singleScanEventHandler
524                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
525                }
526                // TODO(b/27769665) background scans should be failed too if scans fail enough
527                mLastScanSettings = null;
528            }
529        }
530    }
531
532    private void reportPnoScanFailure() {
533        synchronized (mSettingsLock) {
534            if (mLastScanSettings != null && mLastScanSettings.hwPnoScanActive) {
535                if (mLastScanSettings.pnoScanEventHandler != null) {
536                    mLastScanSettings.pnoScanEventHandler.onPnoScanFailed();
537                }
538                // Clean up PNO state, we don't want to continue PNO scanning.
539                mPnoSettings = null;
540                mPnoEventHandler = null;
541                mLastScanSettings = null;
542            }
543        }
544    }
545
546    private void pollLatestScanDataForPno() {
547        synchronized (mSettingsLock) {
548            if (mLastScanSettings == null) {
549                 // got a scan before we started scanning or after scan was canceled
550                return;
551            }
552            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
553            List<ScanResult> hwPnoScanResults = new ArrayList<>();
554            int numFilteredScanResults = 0;
555            for (int i = 0; i < nativeResults.size(); ++i) {
556                ScanResult result = nativeResults.get(i).getScanResult();
557                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
558                if (timestamp_ms > mLastScanSettings.startTime) {
559                    if (mLastScanSettings.hwPnoScanActive) {
560                        hwPnoScanResults.add(result);
561                    }
562                } else {
563                    numFilteredScanResults++;
564                }
565            }
566
567            if (numFilteredScanResults != 0) {
568                Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results.");
569            }
570
571            if (mLastScanSettings.hwPnoScanActive
572                    && mLastScanSettings.pnoScanEventHandler != null) {
573                ScanResult[] pnoScanResultsArray = new ScanResult[hwPnoScanResults.size()];
574                for (int i = 0; i < pnoScanResultsArray.length; ++i) {
575                    ScanResult result = nativeResults.get(i).getScanResult();
576                    pnoScanResultsArray[i] = hwPnoScanResults.get(i);
577                }
578                mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
579            }
580            // mLastScanSettings is for either single/batched scan or pno scan.
581            // We can safely set it to null when pno scan finishes.
582            mLastScanSettings = null;
583        }
584    }
585
586    private void pollLatestScanData() {
587        synchronized (mSettingsLock) {
588            if (mLastScanSettings == null) {
589                 // got a scan before we started scanning or after scan was canceled
590                return;
591            }
592
593            if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
594            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
595            List<ScanResult> singleScanResults = new ArrayList<>();
596            List<ScanResult> backgroundScanResults = new ArrayList<>();
597            int numFilteredScanResults = 0;
598            for (int i = 0; i < nativeResults.size(); ++i) {
599                ScanResult result = nativeResults.get(i).getScanResult();
600                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
601                if (timestamp_ms > mLastScanSettings.startTime) {
602                    if (mLastScanSettings.backgroundScanActive) {
603                        backgroundScanResults.add(result);
604                    }
605                    if (mLastScanSettings.singleScanActive
606                            && mLastScanSettings.singleScanFreqs.containsChannel(
607                                    result.frequency)) {
608                        singleScanResults.add(result);
609                    }
610                } else {
611                    numFilteredScanResults++;
612                }
613            }
614            if (numFilteredScanResults != 0) {
615                Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
616            }
617
618            if (mLastScanSettings.backgroundScanActive) {
619                if (mBackgroundScanEventHandler != null) {
620                    if ((mLastScanSettings.reportEvents
621                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
622                        for (ScanResult scanResult : backgroundScanResults) {
623                            // TODO(b/27506257): Fill in correct bucketsScanned value
624                            mBackgroundScanEventHandler.onFullScanResult(scanResult, 0);
625                        }
626                    }
627                }
628
629                Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR);
630                ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps,
631                            backgroundScanResults.size())];
632                for (int i = 0; i < scanResultsArray.length; ++i) {
633                    scanResultsArray[i] = backgroundScanResults.get(i);
634                }
635
636                if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
637                    // TODO(b/27506257): Fill in correct bucketsScanned value
638                    mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0,
639                                    scanResultsArray));
640                }
641
642                if (mBackgroundScanEventHandler != null) {
643                    if ((mLastScanSettings.reportEvents
644                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
645                            || (mLastScanSettings.reportEvents
646                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0
647                            || (mLastScanSettings.reportEvents
648                                    == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
649                                    && (mBackgroundScanBuffer.size()
650                                            >= (mBackgroundScanBuffer.capacity()
651                                                    * mLastScanSettings.reportPercentThreshold
652                                                    / 100)
653                                            || mBackgroundScanBuffer.size()
654                                            >= mLastScanSettings.reportNumScansThreshold))) {
655                        mBackgroundScanEventHandler
656                                .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
657                    }
658                }
659            }
660
661            if (mLastScanSettings.singleScanActive
662                    && mLastScanSettings.singleScanEventHandler != null) {
663                if (mLastScanSettings.reportSingleScanFullResults) {
664                    for (ScanResult scanResult : singleScanResults) {
665                        // ignore buckets scanned since there is only one bucket for a single scan
666                        mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult,
667                                /* bucketsScanned */ 0);
668                    }
669                }
670                Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
671                mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
672                        mLastScanSettings.singleScanFreqs.isAllChannels(),
673                        singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
674                mLastScanSettings.singleScanEventHandler
675                        .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
676            }
677
678            mLastScanSettings = null;
679        }
680    }
681
682
683    @Override
684    public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
685        synchronized (mSettingsLock) {
686            WifiScanner.ScanData[] results = mBackgroundScanBuffer.get();
687            if (flush) {
688                mBackgroundScanBuffer.clear();
689            }
690            return results;
691        }
692    }
693
694    private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
695        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
696    }
697
698    private void stopHwPnoScan() {
699        mHwPnoDebouncer.stopPnoScan();
700    }
701
702    private void pauseHwPnoScan() {
703        mHwPnoDebouncer.forceStopPnoScan();
704    }
705
706    private boolean restartHwPnoScan(WifiNative.PnoSettings pnoSettings) {
707        mHwPnoDebouncer.forceStopPnoScan();
708        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
709    }
710
711    /**
712     * Hw Pno Scan is required only for disconnected PNO when the device supports it.
713     * @param isConnectedPno Whether this is connected PNO vs disconnected PNO.
714     * @return true if HW PNO scan is required, false otherwise.
715     */
716    private boolean isHwPnoScanRequired(boolean isConnectedPno) {
717        return (!isConnectedPno & mHwPnoScanSupported);
718    }
719
720    private boolean isHwPnoScanRequired() {
721        if (mPnoSettings == null) return false;
722        return isHwPnoScanRequired(mPnoSettings.isConnected);
723    }
724
725    @Override
726    public boolean setHwPnoList(WifiNative.PnoSettings settings,
727            WifiNative.PnoEventHandler eventHandler) {
728        synchronized (mSettingsLock) {
729            if (mPnoSettings != null) {
730                Log.w(TAG, "Already running a PNO scan");
731                return false;
732            }
733            mPnoEventHandler = eventHandler;
734            mPnoSettings = settings;
735            // TODO(b/36276738): Remove this after we fix b/36231150.
736            Log.d(TAG, "processPendingScans in request of setHwPnoList");
737
738            // For wificond based PNO, we start the scan immediately when we set pno list.
739            processPendingScans();
740            return true;
741        }
742    }
743
744    @Override
745    public boolean resetHwPnoList() {
746        synchronized (mSettingsLock) {
747            if (mPnoSettings == null) {
748                Log.w(TAG, "No PNO scan running");
749                return false;
750            }
751            mPnoEventHandler = null;
752            mPnoSettings = null;
753            // For wificond based PNO, we stop the scan immediately when we reset pno list.
754            stopHwPnoScan();
755            return true;
756        }
757    }
758
759    @Override
760    public boolean isHwPnoSupported(boolean isConnectedPno) {
761        // Hw Pno Scan is supported only for disconnected PNO when the device supports it.
762        return isHwPnoScanRequired(isConnectedPno);
763    }
764
765    @Override
766    public boolean shouldScheduleBackgroundScanForHwPno() {
767        return false;
768    }
769
770    private static class LastScanSettings {
771        public long startTime;
772
773        LastScanSettings(long startTime) {
774            this.startTime = startTime;
775        }
776
777        // Background settings
778        public boolean backgroundScanActive = false;
779        public int scanId;
780        public int maxAps;
781        public int reportEvents;
782        public int reportNumScansThreshold;
783        public int reportPercentThreshold;
784
785        public void setBackgroundScan(int scanId, int maxAps, int reportEvents,
786                int reportNumScansThreshold, int reportPercentThreshold) {
787            this.backgroundScanActive = true;
788            this.scanId = scanId;
789            this.maxAps = maxAps;
790            this.reportEvents = reportEvents;
791            this.reportNumScansThreshold = reportNumScansThreshold;
792            this.reportPercentThreshold = reportPercentThreshold;
793        }
794
795        // Single scan settings
796        public boolean singleScanActive = false;
797        public boolean reportSingleScanFullResults;
798        public ChannelCollection singleScanFreqs;
799        public WifiNative.ScanEventHandler singleScanEventHandler;
800
801        public void setSingleScan(boolean reportSingleScanFullResults,
802                ChannelCollection singleScanFreqs,
803                WifiNative.ScanEventHandler singleScanEventHandler) {
804            singleScanActive = true;
805            this.reportSingleScanFullResults = reportSingleScanFullResults;
806            this.singleScanFreqs = singleScanFreqs;
807            this.singleScanEventHandler = singleScanEventHandler;
808        }
809
810        public boolean hwPnoScanActive = false;
811        public WifiNative.PnoNetwork[] pnoNetworkList;
812        public WifiNative.PnoEventHandler pnoScanEventHandler;
813
814        public void setHwPnoScan(
815                WifiNative.PnoNetwork[] pnoNetworkList,
816                WifiNative.PnoEventHandler pnoScanEventHandler) {
817            hwPnoScanActive = true;
818            this.pnoNetworkList = pnoNetworkList;
819            this.pnoScanEventHandler = pnoScanEventHandler;
820        }
821    }
822
823
824    private static class ScanBuffer {
825        private final ArrayDeque<WifiScanner.ScanData> mBuffer;
826        private int mCapacity;
827
828        ScanBuffer(int capacity) {
829            mCapacity = capacity;
830            mBuffer = new ArrayDeque<>(mCapacity);
831        }
832
833        public int size() {
834            return mBuffer.size();
835        }
836
837        public int capacity() {
838            return mCapacity;
839        }
840
841        public boolean isFull() {
842            return size() == mCapacity;
843        }
844
845        public void add(WifiScanner.ScanData scanData) {
846            if (isFull()) {
847                mBuffer.pollFirst();
848            }
849            mBuffer.offerLast(scanData);
850        }
851
852        public void clear() {
853            mBuffer.clear();
854        }
855
856        public WifiScanner.ScanData[] get() {
857            return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]);
858        }
859    }
860
861    /**
862     * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
863     * state too often which is not handled very well by some drivers.
864     * Note: This is not thread safe!
865     */
866    public static class HwPnoDebouncer {
867        public static final String PNO_DEBOUNCER_ALARM_TAG = TAG + "Pno Monitor";
868        private static final int MINIMUM_PNO_GAP_MS = 5 * 1000;
869
870        private final WifiNative mWifiNative;
871        private final AlarmManager mAlarmManager;
872        private final Handler mEventHandler;
873        private final Clock mClock;
874        private long mLastPnoChangeTimeStamp = -1L;
875        private boolean mExpectedPnoState = false;
876        private boolean mCurrentPnoState = false;;
877        private boolean mWaitForTimer = false;
878        private Listener mListener;
879        private WifiNative.PnoSettings mPnoSettings;
880
881        /**
882         * Interface used to indicate PNO scan notifications.
883         */
884        public interface Listener {
885            /**
886             * Used to indicate a delayed PNO scan request failure.
887             */
888            void onPnoScanFailed();
889        }
890
891        public HwPnoDebouncer(WifiNative wifiNative, AlarmManager alarmManager,
892                Handler eventHandler, Clock clock) {
893            mWifiNative = wifiNative;
894            mAlarmManager = alarmManager;
895            mEventHandler = eventHandler;
896            mClock = clock;
897        }
898
899        /**
900         * Enable PNO state in wificond
901         */
902        private boolean startPnoScanInternal() {
903            if (mCurrentPnoState) {
904                if (DBG) Log.d(TAG, "PNO state is already enable");
905                return true;
906            }
907            if (mPnoSettings == null) {
908                Log.e(TAG, "PNO state change to enable failed, no available Pno settings");
909                return false;
910            }
911            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
912            if (mWifiNative.startPnoScan(mPnoSettings)) {
913                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to enable");
914                mCurrentPnoState = true;
915                return true;
916            } else {
917                Log.e(TAG, "PNO state change to enable failed");
918                mCurrentPnoState = false;
919            }
920            return false;
921        }
922
923        /**
924         * Disable PNO state in wificond
925         */
926        private boolean stopPnoScanInternal() {
927            if (!mCurrentPnoState) {
928                if (DBG) Log.d(TAG, "PNO state is already disable");
929                return true;
930            }
931            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
932            if (mWifiNative.stopPnoScan()) {
933                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to disable");
934                mCurrentPnoState = false;
935                return true;
936            } else {
937                Log.e(TAG, "PNO state change to disable failed");
938                mCurrentPnoState = false;
939            }
940            return false;
941        }
942
943        private final AlarmManager.OnAlarmListener mAlarmListener =
944                new AlarmManager.OnAlarmListener() {
945            public void onAlarm() {
946                if (DBG) Log.d(TAG, "PNO timer expired, expected state " + mExpectedPnoState);
947                if (mExpectedPnoState) {
948                    if (!startPnoScanInternal()) {
949                        if (mListener != null) {
950                            mListener.onPnoScanFailed();
951                        }
952                    }
953                } else {
954                    stopPnoScanInternal();
955                }
956                mWaitForTimer = false;
957            }
958        };
959
960        /**
961         * Enable/Disable PNO state. This method will debounce PNO scan requests.
962         * @param enable boolean indicating whether PNO is being enabled or disabled.
963         */
964        private boolean setPnoState(boolean enable) {
965            boolean isSuccess = true;
966            mExpectedPnoState = enable;
967            if (!mWaitForTimer) {
968                long timeDifference = mClock.getElapsedSinceBootMillis() - mLastPnoChangeTimeStamp;
969                if (timeDifference >= MINIMUM_PNO_GAP_MS) {
970                    if (enable) {
971                        isSuccess = startPnoScanInternal();
972                    } else {
973                        isSuccess = stopPnoScanInternal();
974                    }
975                } else {
976                    long alarmTimeout = MINIMUM_PNO_GAP_MS - timeDifference;
977                    Log.d(TAG, "Start PNO timer with delay " + alarmTimeout);
978                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
979                            mClock.getElapsedSinceBootMillis() + alarmTimeout,
980                            PNO_DEBOUNCER_ALARM_TAG,
981                            mAlarmListener, mEventHandler);
982                    mWaitForTimer = true;
983                }
984            }
985            return isSuccess;
986        }
987
988        /**
989         * Start PNO scan
990         */
991        public boolean startPnoScan(WifiNative.PnoSettings pnoSettings, Listener listener) {
992            if (DBG) Log.d(TAG, "Starting PNO scan");
993            mListener = listener;
994            mPnoSettings = pnoSettings;
995            if (!setPnoState(true)) {
996                mListener = null;
997                return false;
998            }
999            return true;
1000        }
1001
1002        /**
1003         * Stop PNO scan
1004         */
1005        public void stopPnoScan() {
1006            if (DBG) Log.d(TAG, "Stopping PNO scan");
1007            setPnoState(false);
1008            mListener = null;
1009        }
1010
1011        /**
1012         * Force stop PNO scanning. This method will bypass the debounce logic and stop PNO
1013         * scan immediately.
1014         */
1015        public void forceStopPnoScan() {
1016            if (DBG) Log.d(TAG, "Force stopping Pno scan");
1017            // Cancel the debounce timer and stop PNO scan.
1018            if (mWaitForTimer) {
1019                mAlarmManager.cancel(mAlarmListener);
1020                mWaitForTimer = false;
1021            }
1022            stopPnoScanInternal();
1023        }
1024    }
1025}
1026