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