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 boolean isDifferentPnoScanSettings(LastScanSettings newScanSettings) {
333        return (mLastScanSettings == null || !Arrays.equals(
334                newScanSettings.pnoNetworkList, mLastScanSettings.pnoNetworkList));
335    }
336
337    private void processPendingScans() {
338        synchronized (mSettingsLock) {
339            // Wait for the active scan result to come back to reschedule other scans,
340            // unless if HW pno scan is running. Hw PNO scans are paused it if there
341            // are other pending scans,
342            if (mLastScanSettings != null && !mLastScanSettings.hwPnoScanActive) {
343                return;
344            }
345
346            ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
347            Set<Integer> hiddenNetworkIdSet = new HashSet<Integer>();
348            final LastScanSettings newScanSettings =
349                    new LastScanSettings(mClock.elapsedRealtime());
350
351            // Update scan settings if there is a pending scan
352            if (!mBackgroundScanPaused) {
353                if (mPendingBackgroundScanSettings != null) {
354                    mBackgroundScanSettings = mPendingBackgroundScanSettings;
355                    mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
356                    mNextBackgroundScanPeriod = 0;
357                    mPendingBackgroundScanSettings = null;
358                    mPendingBackgroundScanEventHandler = null;
359                    mBackgroundScanPeriodPending = true;
360                }
361                if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
362                    int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
363                    for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
364                            ++bucket_id) {
365                        WifiNative.BucketSettings bucket =
366                                mBackgroundScanSettings.buckets[bucket_id];
367                        if (mNextBackgroundScanPeriod % (bucket.period_ms
368                                        / mBackgroundScanSettings.base_period_ms) == 0) {
369                            if ((bucket.report_events
370                                            & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
371                                reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
372                            }
373                            if ((bucket.report_events
374                                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
375                                reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
376                            }
377                            // only no batch if all buckets specify it
378                            if ((bucket.report_events
379                                            & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
380                                reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
381                            }
382
383                            allFreqs.addChannels(bucket);
384                        }
385                    }
386                    if (!allFreqs.isEmpty()) {
387                        newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
388                                mBackgroundScanSettings.max_ap_per_scan, reportEvents,
389                                mBackgroundScanSettings.report_threshold_num_scans,
390                                mBackgroundScanSettings.report_threshold_percent);
391                    }
392
393                    int[] hiddenNetworkIds = mBackgroundScanSettings.hiddenNetworkIds;
394                    if (hiddenNetworkIds != null) {
395                        int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length,
396                                MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
397                        for (int i = 0; i < numHiddenNetworkIds; i++) {
398                            hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
399                        }
400                    }
401
402                    mNextBackgroundScanPeriod++;
403                    mBackgroundScanPeriodPending = false;
404                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
405                            mClock.elapsedRealtime() + mBackgroundScanSettings.base_period_ms,
406                            BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
407                }
408            }
409
410            if (mPendingSingleScanSettings != null) {
411                boolean reportFullResults = false;
412                ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
413                for (int i = 0; i < mPendingSingleScanSettings.num_buckets; ++i) {
414                    WifiNative.BucketSettings bucketSettings =
415                            mPendingSingleScanSettings.buckets[i];
416                    if ((bucketSettings.report_events
417                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
418                        reportFullResults = true;
419                    }
420                    singleScanFreqs.addChannels(bucketSettings);
421                    allFreqs.addChannels(bucketSettings);
422                }
423                newScanSettings.setSingleScan(reportFullResults, singleScanFreqs,
424                        mPendingSingleScanEventHandler);
425                int[] hiddenNetworkIds = mPendingSingleScanSettings.hiddenNetworkIds;
426                if (hiddenNetworkIds != null) {
427                    int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length,
428                            MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
429                    for (int i = 0; i < numHiddenNetworkIds; i++) {
430                        hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
431                    }
432                }
433                mPendingSingleScanSettings = null;
434                mPendingSingleScanEventHandler = null;
435            }
436
437            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
438                    && !allFreqs.isEmpty()) {
439                pauseHwPnoScan();
440                Set<Integer> freqs = allFreqs.getSupplicantScanFreqs();
441                boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet);
442                if (success) {
443                    // TODO handle scan timeout
444                    if (DBG) {
445                        Log.d(TAG, "Starting wifi scan for freqs=" + freqs
446                                + ", background=" + newScanSettings.backgroundScanActive
447                                + ", single=" + newScanSettings.singleScanActive);
448                    }
449                    mLastScanSettings = newScanSettings;
450                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
451                            mClock.elapsedRealtime() + SCAN_TIMEOUT_MS,
452                            TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
453                } else {
454                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
455                    // indicate scan failure async
456                    mEventHandler.post(new Runnable() {
457                            public void run() {
458                                if (newScanSettings.singleScanEventHandler != null) {
459                                    newScanSettings.singleScanEventHandler
460                                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
461                                }
462                            }
463                        });
464                    // TODO(b/27769665) background scans should be failed too if scans fail enough
465                }
466            } else if (isHwPnoScanRequired()) {
467                newScanSettings.setHwPnoScan(mPnoSettings.networkList, mPnoEventHandler);
468                boolean status;
469                // If the PNO network list has changed from the previous request, ensure that
470                // we bypass the debounce logic and restart PNO scan.
471                if (isDifferentPnoScanSettings(newScanSettings)) {
472                    status = restartHwPnoScan();
473                } else {
474                    status = startHwPnoScan();
475                }
476                if (status) {
477                    mLastScanSettings = newScanSettings;
478                } else {
479                    Log.e(TAG, "Failed to start PNO scan");
480                    // indicate scan failure async
481                    mEventHandler.post(new Runnable() {
482                        public void run() {
483                            if (mPnoEventHandler != null) {
484                                mPnoEventHandler.onPnoScanFailed();
485                            }
486                            // Clean up PNO state, we don't want to continue PNO scanning.
487                            mPnoSettings = null;
488                            mPnoEventHandler = null;
489                        }
490                    });
491                }
492            }
493        }
494    }
495
496    @Override
497    public boolean handleMessage(Message msg) {
498        switch(msg.what) {
499            case WifiMonitor.SCAN_FAILED_EVENT:
500                Log.w(TAG, "Scan failed");
501                mAlarmManager.cancel(mScanTimeoutListener);
502                reportScanFailure();
503                processPendingScans();
504                break;
505            case WifiMonitor.SCAN_RESULTS_EVENT:
506                mAlarmManager.cancel(mScanTimeoutListener);
507                pollLatestScanData();
508                processPendingScans();
509                break;
510            default:
511                // ignore unknown event
512        }
513        return true;
514    }
515
516    private void reportScanFailure() {
517        synchronized (mSettingsLock) {
518            if (mLastScanSettings != null) {
519                if (mLastScanSettings.singleScanEventHandler != null) {
520                    mLastScanSettings.singleScanEventHandler
521                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
522                }
523                // TODO(b/27769665) background scans should be failed too if scans fail enough
524                mLastScanSettings = null;
525            }
526        }
527    }
528
529    private void reportPnoScanFailure() {
530        synchronized (mSettingsLock) {
531            if (mLastScanSettings != null && mLastScanSettings.hwPnoScanActive) {
532                if (mLastScanSettings.pnoScanEventHandler != null) {
533                    mLastScanSettings.pnoScanEventHandler.onPnoScanFailed();
534                }
535                // Clean up PNO state, we don't want to continue PNO scanning.
536                mPnoSettings = null;
537                mPnoEventHandler = null;
538                mLastScanSettings = null;
539            }
540        }
541    }
542
543    private void pollLatestScanData() {
544        synchronized (mSettingsLock) {
545            if (mLastScanSettings == null) {
546                 // got a scan before we started scanning or after scan was canceled
547                return;
548            }
549
550            if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
551            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
552            List<ScanResult> singleScanResults = new ArrayList<>();
553            List<ScanResult> backgroundScanResults = new ArrayList<>();
554            List<ScanResult> hwPnoScanResults = new ArrayList<>();
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.backgroundScanActive) {
560                        backgroundScanResults.add(result);
561                    }
562                    if (mLastScanSettings.singleScanActive
563                            && mLastScanSettings.singleScanFreqs.containsChannel(
564                                    result.frequency)) {
565                        singleScanResults.add(result);
566                    }
567                    if (mLastScanSettings.hwPnoScanActive) {
568                        hwPnoScanResults.add(result);
569                    }
570                } else {
571                    // was a cached result in wpa_supplicant
572                }
573            }
574
575            if (mLastScanSettings.backgroundScanActive) {
576                if (mBackgroundScanEventHandler != null) {
577                    if ((mLastScanSettings.reportEvents
578                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
579                        for (ScanResult scanResult : backgroundScanResults) {
580                            // TODO(b/27506257): Fill in correct bucketsScanned value
581                            mBackgroundScanEventHandler.onFullScanResult(scanResult, 0);
582                        }
583                    }
584                }
585
586                Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR);
587                ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps,
588                            backgroundScanResults.size())];
589                for (int i = 0; i < scanResultsArray.length; ++i) {
590                    scanResultsArray[i] = backgroundScanResults.get(i);
591                }
592
593                if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
594                    // TODO(b/27506257): Fill in correct bucketsScanned value
595                    mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0,
596                                    scanResultsArray));
597                }
598
599                if (mBackgroundScanEventHandler != null) {
600                    if ((mLastScanSettings.reportEvents
601                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
602                            || (mLastScanSettings.reportEvents
603                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0
604                            || (mLastScanSettings.reportEvents
605                                    == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
606                                    && (mBackgroundScanBuffer.size()
607                                            >= (mBackgroundScanBuffer.capacity()
608                                                    * mLastScanSettings.reportPercentThreshold
609                                                    / 100)
610                                            || mBackgroundScanBuffer.size()
611                                            >= mLastScanSettings.reportNumScansThreshold))) {
612                        mBackgroundScanEventHandler
613                                .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
614                    }
615                }
616
617                if (mHotlistHandler != null) {
618                    int event = mHotlistChangeBuffer.processScan(backgroundScanResults);
619                    if ((event & ChangeBuffer.EVENT_FOUND) != 0) {
620                        mHotlistHandler.onHotlistApFound(
621                                mHotlistChangeBuffer.getLastResults(ChangeBuffer.EVENT_FOUND));
622                    }
623                    if ((event & ChangeBuffer.EVENT_LOST) != 0) {
624                        mHotlistHandler.onHotlistApLost(
625                                mHotlistChangeBuffer.getLastResults(ChangeBuffer.EVENT_LOST));
626                    }
627                }
628            }
629
630            if (mLastScanSettings.singleScanActive
631                    && mLastScanSettings.singleScanEventHandler != null) {
632                if (mLastScanSettings.reportSingleScanFullResults) {
633                    for (ScanResult scanResult : singleScanResults) {
634                        // ignore buckets scanned since there is only one bucket for a single scan
635                        mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult,
636                                /* bucketsScanned */ 0);
637                    }
638                }
639                Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
640                mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
641                        mLastScanSettings.singleScanFreqs.isAllChannels(),
642                        singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
643                mLastScanSettings.singleScanEventHandler
644                        .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
645            }
646
647            if (mLastScanSettings.hwPnoScanActive
648                    && mLastScanSettings.pnoScanEventHandler != null) {
649                ScanResult[] pnoScanResultsArray = new ScanResult[hwPnoScanResults.size()];
650                for (int i = 0; i < pnoScanResultsArray.length; ++i) {
651                    pnoScanResultsArray[i] = hwPnoScanResults.get(i);
652                }
653                mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
654            }
655
656            mLastScanSettings = null;
657        }
658    }
659
660
661    @Override
662    public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
663        synchronized (mSettingsLock) {
664            WifiScanner.ScanData[] results = mBackgroundScanBuffer.get();
665            if (flush) {
666                mBackgroundScanBuffer.clear();
667            }
668            return results;
669        }
670    }
671
672    private boolean setNetworkPriorities(WifiNative.PnoNetwork[] networkList) {
673        if (networkList != null) {
674            if (DBG) Log.i(TAG, "Enable network and Set priorities for PNO.");
675            for (WifiNative.PnoNetwork network : networkList) {
676                if (!mWifiNative.setNetworkVariable(network.networkId,
677                        WifiConfiguration.priorityVarName,
678                        Integer.toString(network.priority))) {
679                    Log.e(TAG, "Set priority failed for: " + network.networkId);
680                    return false;
681                }
682                if (!mWifiNative.enableNetworkWithoutConnect(network.networkId)) {
683                    Log.e(TAG, "Enable network failed for: " + network.networkId);
684                    return false;
685                }
686            }
687        }
688        return true;
689    }
690
691    private boolean startHwPnoScan() {
692        return mHwPnoDebouncer.startPnoScan(mHwPnoDebouncerListener);
693    }
694
695    private void stopHwPnoScan() {
696        mHwPnoDebouncer.stopPnoScan();
697    }
698
699    private void pauseHwPnoScan() {
700        mHwPnoDebouncer.forceStopPnoScan();
701    }
702
703    private boolean restartHwPnoScan() {
704        mHwPnoDebouncer.forceStopPnoScan();
705        return mHwPnoDebouncer.startPnoScan(mHwPnoDebouncerListener);
706    }
707
708    /**
709     * Hw Pno Scan is required only for disconnected PNO when the device supports it.
710     * @param isConnectedPno Whether this is connected PNO vs disconnected PNO.
711     * @return true if HW PNO scan is required, false otherwise.
712     */
713    private boolean isHwPnoScanRequired(boolean isConnectedPno) {
714        return (!isConnectedPno & mHwPnoScanSupported);
715    }
716
717    private boolean isHwPnoScanRequired() {
718        if (mPnoSettings == null) return false;
719        return isHwPnoScanRequired(mPnoSettings.isConnected);
720    }
721
722    @Override
723    public boolean setHwPnoList(WifiNative.PnoSettings settings,
724            WifiNative.PnoEventHandler eventHandler) {
725        synchronized (mSettingsLock) {
726            if (mPnoSettings != null) {
727                Log.w(TAG, "Already running a PNO scan");
728                return false;
729            }
730            mPnoEventHandler = eventHandler;
731            mPnoSettings = settings;
732            if (!setNetworkPriorities(settings.networkList)) return false;
733            // For supplicant based PNO, we start the scan immediately when we set pno list.
734            processPendingScans();
735            return true;
736        }
737    }
738
739    @Override
740    public boolean resetHwPnoList() {
741        synchronized (mSettingsLock) {
742            if (mPnoSettings == null) {
743                Log.w(TAG, "No PNO scan running");
744                return false;
745            }
746            mPnoEventHandler = null;
747            mPnoSettings = null;
748            // For supplicant based PNO, we stop the scan immediately when we reset pno list.
749            stopHwPnoScan();
750            return true;
751        }
752    }
753
754    @Override
755    public boolean isHwPnoSupported(boolean isConnectedPno) {
756        // Hw Pno Scan is supported only for disconnected PNO when the device supports it.
757        return isHwPnoScanRequired(isConnectedPno);
758    }
759
760    @Override
761    public boolean shouldScheduleBackgroundScanForHwPno() {
762        return false;
763    }
764
765    @Override
766    public boolean setHotlist(WifiScanner.HotlistSettings settings,
767            WifiNative.HotlistEventHandler eventHandler) {
768        if (settings == null || eventHandler == null) {
769            return false;
770        }
771        synchronized (mSettingsLock) {
772            mHotlistHandler = eventHandler;
773            mHotlistChangeBuffer.setSettings(settings.bssidInfos, settings.apLostThreshold, 1);
774            return true;
775        }
776    }
777
778    @Override
779    public void resetHotlist() {
780        synchronized (mSettingsLock) {
781            mHotlistChangeBuffer.clearSettings();
782            mHotlistHandler = null;
783        }
784    }
785
786    /*
787     * Significant Wifi Change API is not implemented
788     */
789    @Override
790    public boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings,
791            WifiNative.SignificantWifiChangeEventHandler handler) {
792        return false;
793    }
794    @Override
795    public void untrackSignificantWifiChange() {}
796
797
798    private static class LastScanSettings {
799        public long startTime;
800
801        public LastScanSettings(long startTime) {
802            this.startTime = startTime;
803        }
804
805        // Background settings
806        public boolean backgroundScanActive = false;
807        public int scanId;
808        public int maxAps;
809        public int reportEvents;
810        public int reportNumScansThreshold;
811        public int reportPercentThreshold;
812
813        public void setBackgroundScan(int scanId, int maxAps, int reportEvents,
814                int reportNumScansThreshold, int reportPercentThreshold) {
815            this.backgroundScanActive = true;
816            this.scanId = scanId;
817            this.maxAps = maxAps;
818            this.reportEvents = reportEvents;
819            this.reportNumScansThreshold = reportNumScansThreshold;
820            this.reportPercentThreshold = reportPercentThreshold;
821        }
822
823        // Single scan settings
824        public boolean singleScanActive = false;
825        public boolean reportSingleScanFullResults;
826        public ChannelCollection singleScanFreqs;
827        public WifiNative.ScanEventHandler singleScanEventHandler;
828
829        public void setSingleScan(boolean reportSingleScanFullResults,
830                ChannelCollection singleScanFreqs,
831                WifiNative.ScanEventHandler singleScanEventHandler) {
832            singleScanActive = true;
833            this.reportSingleScanFullResults = reportSingleScanFullResults;
834            this.singleScanFreqs = singleScanFreqs;
835            this.singleScanEventHandler = singleScanEventHandler;
836        }
837
838        public boolean hwPnoScanActive = false;
839        public WifiNative.PnoNetwork[] pnoNetworkList;
840        public WifiNative.PnoEventHandler pnoScanEventHandler;
841
842        public void setHwPnoScan(
843                WifiNative.PnoNetwork[] pnoNetworkList,
844                WifiNative.PnoEventHandler pnoScanEventHandler) {
845            hwPnoScanActive = true;
846            this.pnoNetworkList = pnoNetworkList;
847            this.pnoScanEventHandler = pnoScanEventHandler;
848        }
849    }
850
851
852    private static class ScanBuffer {
853        private final ArrayDeque<WifiScanner.ScanData> mBuffer;
854        private int mCapacity;
855
856        public ScanBuffer(int capacity) {
857            mCapacity = capacity;
858            mBuffer = new ArrayDeque<>(mCapacity);
859        }
860
861        public int size() {
862            return mBuffer.size();
863        }
864
865        public int capacity() {
866            return mCapacity;
867        }
868
869        public boolean isFull() {
870            return size() == mCapacity;
871        }
872
873        public void add(WifiScanner.ScanData scanData) {
874            if (isFull()) {
875                mBuffer.pollFirst();
876            }
877            mBuffer.offerLast(scanData);
878        }
879
880        public void clear() {
881            mBuffer.clear();
882        }
883
884        public WifiScanner.ScanData[] get() {
885            return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]);
886        }
887    }
888
889    private static class ChangeBuffer {
890        public static int EVENT_NONE = 0;
891        public static int EVENT_LOST = 1;
892        public static int EVENT_FOUND = 2;
893
894        public static int STATE_FOUND = 0;
895
896        private WifiScanner.BssidInfo[] mBssidInfos = null;
897        private int mApLostThreshold;
898        private int mMinEvents;
899        private int[] mLostCount = null;
900        private ScanResult[] mMostRecentResult = null;
901        private int[] mPendingEvent = null;
902        private boolean mFiredEvents = false;
903
904        private static ScanResult findResult(List<ScanResult> results, String bssid) {
905            for (int i = 0; i < results.size(); ++i) {
906                if (bssid.equalsIgnoreCase(results.get(i).BSSID)) {
907                    return results.get(i);
908                }
909            }
910            return null;
911        }
912
913        public void setSettings(WifiScanner.BssidInfo[] bssidInfos, int apLostThreshold,
914                                int minEvents) {
915            mBssidInfos = bssidInfos;
916            if (apLostThreshold <= 0) {
917                mApLostThreshold = 1;
918            } else {
919                mApLostThreshold = apLostThreshold;
920            }
921            mMinEvents = minEvents;
922            if (bssidInfos != null) {
923                mLostCount = new int[bssidInfos.length];
924                Arrays.fill(mLostCount, mApLostThreshold); // default to lost
925                mMostRecentResult = new ScanResult[bssidInfos.length];
926                mPendingEvent = new int[bssidInfos.length];
927                mFiredEvents = false;
928            } else {
929                mLostCount = null;
930                mMostRecentResult = null;
931                mPendingEvent = null;
932            }
933        }
934
935        public void clearSettings() {
936            setSettings(null, 0, 0);
937        }
938
939        /**
940         * Get the most recent scan results for APs that triggered the given event on the last call
941         * to {@link #processScan}.
942         */
943        public ScanResult[] getLastResults(int event) {
944            ArrayList<ScanResult> results = new ArrayList<>();
945            for (int i = 0; i < mLostCount.length; ++i) {
946                if (mPendingEvent[i] == event) {
947                    results.add(mMostRecentResult[i]);
948                }
949            }
950            return results.toArray(new ScanResult[results.size()]);
951        }
952
953        /**
954         * Process the supplied scan results and determine if any events should be generated based
955         * on the configured settings
956         * @return The events that occurred
957         */
958        public int processScan(List<ScanResult> scanResults) {
959            if (mBssidInfos == null) {
960                return EVENT_NONE;
961            }
962
963            // clear events from last time
964            if (mFiredEvents) {
965                mFiredEvents = false;
966                for (int i = 0; i < mLostCount.length; ++i) {
967                    mPendingEvent[i] = EVENT_NONE;
968                }
969            }
970
971            int eventCount = 0;
972            int eventType = EVENT_NONE;
973            for (int i = 0; i < mLostCount.length; ++i) {
974                ScanResult result = findResult(scanResults, mBssidInfos[i].bssid);
975                int rssi = Integer.MIN_VALUE;
976                if (result != null) {
977                    mMostRecentResult[i] = result;
978                    rssi = result.level;
979                }
980
981                if (rssi < mBssidInfos[i].low) {
982                    if (mLostCount[i] < mApLostThreshold) {
983                        mLostCount[i]++;
984
985                        if (mLostCount[i] >= mApLostThreshold) {
986                            if (mPendingEvent[i] == EVENT_FOUND) {
987                                mPendingEvent[i] = EVENT_NONE;
988                            } else {
989                                mPendingEvent[i] = EVENT_LOST;
990                            }
991                        }
992                    }
993                } else {
994                    if (mLostCount[i] >= mApLostThreshold) {
995                        if (mPendingEvent[i] == EVENT_LOST) {
996                            mPendingEvent[i] = EVENT_NONE;
997                        } else {
998                            mPendingEvent[i] = EVENT_FOUND;
999                        }
1000                    }
1001                    mLostCount[i] = STATE_FOUND;
1002                }
1003                if (DBG) {
1004                    Log.d(TAG, "ChangeBuffer BSSID: " + mBssidInfos[i].bssid + "=" + mLostCount[i]
1005                            + ", " + mPendingEvent[i] + ", rssi=" + rssi);
1006                }
1007                if (mPendingEvent[i] != EVENT_NONE) {
1008                    ++eventCount;
1009                    eventType |= mPendingEvent[i];
1010                }
1011            }
1012            if (DBG) Log.d(TAG, "ChangeBuffer events count=" + eventCount + ": " + eventType);
1013            if (eventCount >= mMinEvents) {
1014                mFiredEvents = true;
1015                return eventType;
1016            }
1017            return EVENT_NONE;
1018        }
1019    }
1020
1021    /**
1022     * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
1023     * state too often which is not handled very well by some drivers.
1024     * Note: This is not thread safe!
1025     */
1026    public static class HwPnoDebouncer {
1027        public static final String PNO_DEBOUNCER_ALARM_TAG = TAG + "Pno Monitor";
1028        private static final int MINIMUM_PNO_GAP_MS = 5 * 1000;
1029
1030        private final WifiNative mWifiNative;
1031        private final AlarmManager mAlarmManager;
1032        private final Handler mEventHandler;
1033        private final Clock mClock;
1034        private long mLastPnoChangeTimeStamp = -1L;
1035        private boolean mExpectedPnoState = false;
1036        private boolean mCurrentPnoState = false;;
1037        private boolean mWaitForTimer = false;
1038        private Listener mListener;
1039
1040        /**
1041         * Interface used to indicate PNO scan notifications.
1042         */
1043        public interface Listener {
1044            /**
1045             * Used to indicate a delayed PNO scan request failure.
1046             */
1047            void onPnoScanFailed();
1048        }
1049
1050        public HwPnoDebouncer(WifiNative wifiNative, AlarmManager alarmManager,
1051                Handler eventHandler, Clock clock) {
1052            mWifiNative = wifiNative;
1053            mAlarmManager = alarmManager;
1054            mEventHandler = eventHandler;
1055            mClock = clock;
1056        }
1057
1058        /**
1059         * Enable/Disable PNO state in wpa_supplicant
1060         * @param enable boolean indicating whether PNO is being enabled or disabled.
1061         */
1062        private boolean updatePnoState(boolean enable) {
1063            if (mCurrentPnoState == enable) {
1064                if (DBG) Log.d(TAG, "PNO state is already " + enable);
1065                return true;
1066            }
1067            mLastPnoChangeTimeStamp = mClock.elapsedRealtime();
1068            if (mWifiNative.setPnoScan(enable)) {
1069                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to " + enable);
1070                mCurrentPnoState = enable;
1071                return true;
1072            } else {
1073                Log.e(TAG, "PNO state change to " + enable + " failed");
1074                mCurrentPnoState = false;
1075                return false;
1076            }
1077        }
1078
1079        private final AlarmManager.OnAlarmListener mAlarmListener =
1080                new AlarmManager.OnAlarmListener() {
1081            public void onAlarm() {
1082                if (DBG) Log.d(TAG, "PNO timer expired, expected state " + mExpectedPnoState);
1083                if (!updatePnoState(mExpectedPnoState)) {
1084                    if (mListener != null) {
1085                        mListener.onPnoScanFailed();
1086                    }
1087                }
1088                mWaitForTimer = false;
1089            }
1090        };
1091
1092        /**
1093         * Enable/Disable PNO state. This method will debounce PNO scan requests.
1094         * @param enable boolean indicating whether PNO is being enabled or disabled.
1095         */
1096        private boolean setPnoState(boolean enable) {
1097            boolean isSuccess = true;
1098            mExpectedPnoState = enable;
1099            if (!mWaitForTimer) {
1100                long timeDifference = mClock.elapsedRealtime() - mLastPnoChangeTimeStamp;
1101                if (timeDifference >= MINIMUM_PNO_GAP_MS) {
1102                    isSuccess = updatePnoState(enable);
1103                } else {
1104                    long alarmTimeout = MINIMUM_PNO_GAP_MS - timeDifference;
1105                    Log.d(TAG, "Start PNO timer with delay " + alarmTimeout);
1106                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
1107                            mClock.elapsedRealtime() + alarmTimeout, PNO_DEBOUNCER_ALARM_TAG,
1108                            mAlarmListener, mEventHandler);
1109                    mWaitForTimer = true;
1110                }
1111            }
1112            return isSuccess;
1113        }
1114
1115        /**
1116         * Start PNO scan
1117         */
1118        public boolean startPnoScan(Listener listener) {
1119            if (DBG) Log.d(TAG, "Starting PNO scan");
1120            mListener = listener;
1121            if (!setPnoState(true)) {
1122                mListener = null;
1123                return false;
1124            }
1125            return true;
1126        }
1127
1128        /**
1129         * Stop PNO scan
1130         */
1131        public void stopPnoScan() {
1132            if (DBG) Log.d(TAG, "Stopping PNO scan");
1133            setPnoState(false);
1134            mListener = null;
1135        }
1136
1137        /**
1138         * Force stop PNO scanning. This method will bypass the debounce logic and stop PNO
1139         * scan immediately.
1140         */
1141        public void forceStopPnoScan() {
1142            if (DBG) Log.d(TAG, "Force stopping Pno scan");
1143            // Cancel the debounce timer and stop PNO scan.
1144            if (mWaitForTimer) {
1145                mAlarmManager.cancel(mAlarmListener);
1146                mWaitForTimer = false;
1147            }
1148            updatePnoState(false);
1149        }
1150    }
1151}
1152