1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.scanner;
18
19import android.app.AlarmManager;
20import android.content.Context;
21import android.net.wifi.ScanResult;
22import android.net.wifi.WifiScanner;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.util.Log;
27
28import com.android.internal.R;
29import com.android.server.wifi.Clock;
30import com.android.server.wifi.ScanDetail;
31import com.android.server.wifi.WifiMonitor;
32import com.android.server.wifi.WifiNative;
33import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
34import com.android.server.wifi.util.ScanResultUtil;
35
36import java.io.FileDescriptor;
37import java.io.PrintWriter;
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Set;
43import java.util.stream.Collectors;
44
45import javax.annotation.concurrent.GuardedBy;
46
47/**
48 * Implementation of the WifiScanner HAL API that uses wificond to perform all scans
49 * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method.
50 */
51public class WificondScannerImpl extends WifiScannerImpl implements Handler.Callback {
52    private static final String TAG = "WificondScannerImpl";
53    private static final boolean DBG = false;
54
55    public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
56    // Max number of networks that can be specified to wificond per scan request
57    public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
58
59    private static final int SCAN_BUFFER_CAPACITY = 10;
60    private static final int MAX_APS_PER_SCAN = 32;
61    private static final int MAX_SCAN_BUCKETS = 16;
62
63    private final Context mContext;
64    private final String mIfaceName;
65    private final WifiNative mWifiNative;
66    private final WifiMonitor mWifiMonitor;
67    private final AlarmManager mAlarmManager;
68    private final Handler mEventHandler;
69    private final ChannelHelper mChannelHelper;
70    private final Clock mClock;
71
72    private final Object mSettingsLock = new Object();
73
74    private ArrayList<ScanDetail> mNativeScanResults;
75    private ArrayList<ScanDetail> mNativePnoScanResults;
76    private WifiScanner.ScanData mLatestSingleScanResult =
77            new WifiScanner.ScanData(0, 0, new ScanResult[0]);
78
79    // Settings for the currently running single scan, null if no scan active
80    private LastScanSettings mLastScanSettings = null;
81    // Settings for the currently running pno scan, null if no scan active
82    private LastPnoScanSettings mLastPnoScanSettings = null;
83
84    private final boolean mHwPnoScanSupported;
85
86    /**
87     * Duration to wait before timing out a scan.
88     *
89     * The expected behavior is that the hardware will return a failed scan if it does not
90     * complete, but timeout just in case it does not.
91     */
92    private static final long SCAN_TIMEOUT_MS = 15000;
93
94    @GuardedBy("mSettingsLock")
95    private AlarmManager.OnAlarmListener mScanTimeoutListener;
96
97    public WificondScannerImpl(Context context, String ifaceName, WifiNative wifiNative,
98                               WifiMonitor wifiMonitor, ChannelHelper channelHelper,
99                               Looper looper, Clock clock) {
100        mContext = context;
101        mIfaceName = ifaceName;
102        mWifiNative = wifiNative;
103        mWifiMonitor = wifiMonitor;
104        mChannelHelper = channelHelper;
105        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
106        mEventHandler = new Handler(looper, this);
107        mClock = clock;
108
109        // Check if the device supports HW PNO scans.
110        mHwPnoScanSupported = mContext.getResources().getBoolean(
111                R.bool.config_wifi_background_scan_support);
112
113        wifiMonitor.registerHandler(mIfaceName,
114                WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
115        wifiMonitor.registerHandler(mIfaceName,
116                WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
117        wifiMonitor.registerHandler(mIfaceName,
118                WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
119    }
120
121    @Override
122    public void cleanup() {
123        synchronized (mSettingsLock) {
124            stopHwPnoScan();
125            mLastScanSettings = null; // finally clear any active scan
126            mLastPnoScanSettings = null; // finally clear any active scan
127            mWifiMonitor.deregisterHandler(mIfaceName,
128                    WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
129            mWifiMonitor.deregisterHandler(mIfaceName,
130                    WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
131            mWifiMonitor.deregisterHandler(mIfaceName,
132                    WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
133        }
134    }
135
136    @Override
137    public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) {
138        capabilities.max_scan_cache_size = Integer.MAX_VALUE;
139        capabilities.max_scan_buckets = MAX_SCAN_BUCKETS;
140        capabilities.max_ap_cache_per_scan = MAX_APS_PER_SCAN;
141        capabilities.max_rssi_sample_size = 8;
142        capabilities.max_scan_reporting_threshold = SCAN_BUFFER_CAPACITY;
143        return true;
144    }
145
146    @Override
147    public ChannelHelper getChannelHelper() {
148        return mChannelHelper;
149    }
150
151    @Override
152    public boolean startSingleScan(WifiNative.ScanSettings settings,
153            WifiNative.ScanEventHandler eventHandler) {
154        if (eventHandler == null || settings == null) {
155            Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings
156                    + ",eventHandler=" + eventHandler);
157            return false;
158        }
159        synchronized (mSettingsLock) {
160            if (mLastScanSettings != null) {
161                Log.w(TAG, "A single scan is already running");
162                return false;
163            }
164
165            ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
166            boolean reportFullResults = false;
167
168            for (int i = 0; i < settings.num_buckets; ++i) {
169                WifiNative.BucketSettings bucketSettings = settings.buckets[i];
170                if ((bucketSettings.report_events
171                                & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
172                    reportFullResults = true;
173                }
174                allFreqs.addChannels(bucketSettings);
175            }
176
177            Set<String> hiddenNetworkSSIDSet = new HashSet<>();
178            if (settings.hiddenNetworks != null) {
179                int numHiddenNetworks =
180                        Math.min(settings.hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
181                for (int i = 0; i < numHiddenNetworks; i++) {
182                    hiddenNetworkSSIDSet.add(settings.hiddenNetworks[i].ssid);
183                }
184            }
185            mLastScanSettings = new LastScanSettings(
186                        mClock.getElapsedSinceBootMillis(),
187                        reportFullResults, allFreqs, eventHandler);
188
189            boolean success = false;
190            Set<Integer> freqs;
191            if (!allFreqs.isEmpty()) {
192                freqs = allFreqs.getScanFreqs();
193                success = mWifiNative.scan(
194                        mIfaceName, settings.scanType, freqs, hiddenNetworkSSIDSet);
195                if (!success) {
196                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
197                }
198            } else {
199                // There is a scan request but no available channels could be scanned for.
200                // We regard it as a scan failure in this case.
201                Log.e(TAG, "Failed to start scan because there is no available channel to scan");
202            }
203            if (success) {
204                if (DBG) {
205                    Log.d(TAG, "Starting wifi scan for freqs=" + freqs);
206                }
207
208                mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
209                    @Override public void onAlarm() {
210                        handleScanTimeout();
211                    }
212                };
213
214                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
215                        mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
216                        TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
217            } else {
218                // indicate scan failure async
219                mEventHandler.post(new Runnable() {
220                        @Override public void run() {
221                            reportScanFailure();
222                        }
223                    });
224            }
225
226            return true;
227        }
228    }
229
230    @Override
231    public WifiScanner.ScanData getLatestSingleScanResults() {
232        return mLatestSingleScanResult;
233    }
234
235    @Override
236    public boolean startBatchedScan(WifiNative.ScanSettings settings,
237            WifiNative.ScanEventHandler eventHandler) {
238        Log.w(TAG, "startBatchedScan() is not supported");
239        return false;
240    }
241
242    @Override
243    public void stopBatchedScan() {
244        Log.w(TAG, "stopBatchedScan() is not supported");
245    }
246
247    @Override
248    public void pauseBatchedScan() {
249        Log.w(TAG, "pauseBatchedScan() is not supported");
250    }
251
252    @Override
253    public void restartBatchedScan() {
254        Log.w(TAG, "restartBatchedScan() is not supported");
255    }
256
257    private void handleScanTimeout() {
258        synchronized (mSettingsLock) {
259            Log.e(TAG, "Timed out waiting for scan result from wificond");
260            reportScanFailure();
261            mScanTimeoutListener = null;
262        }
263    }
264
265    @Override
266    public boolean handleMessage(Message msg) {
267        switch(msg.what) {
268            case WifiMonitor.SCAN_FAILED_EVENT:
269                Log.w(TAG, "Scan failed");
270                cancelScanTimeout();
271                reportScanFailure();
272                break;
273            case WifiMonitor.PNO_SCAN_RESULTS_EVENT:
274                pollLatestScanDataForPno();
275                break;
276            case WifiMonitor.SCAN_RESULTS_EVENT:
277                cancelScanTimeout();
278                pollLatestScanData();
279                break;
280            default:
281                // ignore unknown event
282        }
283        return true;
284    }
285
286    private void cancelScanTimeout() {
287        synchronized (mSettingsLock) {
288            if (mScanTimeoutListener != null) {
289                mAlarmManager.cancel(mScanTimeoutListener);
290                mScanTimeoutListener = null;
291            }
292        }
293    }
294
295    private void reportScanFailure() {
296        synchronized (mSettingsLock) {
297            if (mLastScanSettings != null) {
298                if (mLastScanSettings.singleScanEventHandler != null) {
299                    mLastScanSettings.singleScanEventHandler
300                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
301                }
302                mLastScanSettings = null;
303            }
304        }
305    }
306
307    private void reportPnoScanFailure() {
308        synchronized (mSettingsLock) {
309            if (mLastPnoScanSettings != null) {
310                if (mLastPnoScanSettings.pnoScanEventHandler != null) {
311                    mLastPnoScanSettings.pnoScanEventHandler.onPnoScanFailed();
312                }
313                // Clean up PNO state, we don't want to continue PNO scanning.
314                mLastPnoScanSettings = null;
315            }
316        }
317    }
318
319    private void pollLatestScanDataForPno() {
320        synchronized (mSettingsLock) {
321            if (mLastPnoScanSettings == null) {
322                 // got a scan before we started scanning or after scan was canceled
323                return;
324            }
325            mNativePnoScanResults = mWifiNative.getPnoScanResults(mIfaceName);
326            List<ScanResult> hwPnoScanResults = new ArrayList<>();
327            int numFilteredScanResults = 0;
328            for (int i = 0; i < mNativePnoScanResults.size(); ++i) {
329                ScanResult result = mNativePnoScanResults.get(i).getScanResult();
330                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
331                if (timestamp_ms > mLastPnoScanSettings.startTime) {
332                    hwPnoScanResults.add(result);
333                } else {
334                    numFilteredScanResults++;
335                }
336            }
337
338            if (numFilteredScanResults != 0) {
339                Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results.");
340            }
341
342            if (mLastPnoScanSettings.pnoScanEventHandler != null) {
343                ScanResult[] pnoScanResultsArray =
344                        hwPnoScanResults.toArray(new ScanResult[hwPnoScanResults.size()]);
345                mLastPnoScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
346            }
347        }
348    }
349
350    /**
351     * Check if the provided channel collection contains all the channels.
352     */
353    private static boolean isAllChannelsScanned(ChannelCollection channelCollection) {
354        // TODO(b/62253332): Get rid of this hack.
355        // We're treating 2g + 5g and 2g + 5g + dfs as all channels scanned to work around
356        // the lack of a proper cache.
357        return (channelCollection.containsBand(WifiScanner.WIFI_BAND_24_GHZ)
358                && channelCollection.containsBand(WifiScanner.WIFI_BAND_5_GHZ));
359    }
360
361    private void pollLatestScanData() {
362        synchronized (mSettingsLock) {
363            if (mLastScanSettings == null) {
364                 // got a scan before we started scanning or after scan was canceled
365                return;
366            }
367
368            mNativeScanResults = mWifiNative.getScanResults(mIfaceName);
369            List<ScanResult> singleScanResults = new ArrayList<>();
370            int numFilteredScanResults = 0;
371            for (int i = 0; i < mNativeScanResults.size(); ++i) {
372                ScanResult result = mNativeScanResults.get(i).getScanResult();
373                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
374                if (timestamp_ms > mLastScanSettings.startTime) {
375                    if (mLastScanSettings.singleScanFreqs.containsChannel(
376                                    result.frequency)) {
377                        singleScanResults.add(result);
378                    }
379                } else {
380                    numFilteredScanResults++;
381                }
382            }
383            if (numFilteredScanResults != 0) {
384                Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
385            }
386
387            if (mLastScanSettings.singleScanEventHandler != null) {
388                if (mLastScanSettings.reportSingleScanFullResults) {
389                    for (ScanResult scanResult : singleScanResults) {
390                        // ignore buckets scanned since there is only one bucket for a single scan
391                        mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult,
392                                /* bucketsScanned */ 0);
393                    }
394                }
395                Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
396                mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
397                        isAllChannelsScanned(mLastScanSettings.singleScanFreqs),
398                        singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
399                mLastScanSettings.singleScanEventHandler
400                        .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
401            }
402
403            mLastScanSettings = null;
404        }
405    }
406
407
408    @Override
409    public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
410        return null;
411    }
412
413    private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
414        return mWifiNative.startPnoScan(mIfaceName, pnoSettings);
415    }
416
417    private void stopHwPnoScan() {
418        mWifiNative.stopPnoScan(mIfaceName);
419    }
420
421    /**
422     * Hw Pno Scan is required only for disconnected PNO when the device supports it.
423     * @param isConnectedPno Whether this is connected PNO vs disconnected PNO.
424     * @return true if HW PNO scan is required, false otherwise.
425     */
426    private boolean isHwPnoScanRequired(boolean isConnectedPno) {
427        return (!isConnectedPno && mHwPnoScanSupported);
428    }
429
430    @Override
431    public boolean setHwPnoList(WifiNative.PnoSettings settings,
432            WifiNative.PnoEventHandler eventHandler) {
433        synchronized (mSettingsLock) {
434            if (mLastPnoScanSettings != null) {
435                Log.w(TAG, "Already running a PNO scan");
436                return false;
437            }
438            if (!isHwPnoScanRequired(settings.isConnected)) {
439                return false;
440            }
441
442            if (startHwPnoScan(settings)) {
443                mLastPnoScanSettings = new LastPnoScanSettings(
444                            mClock.getElapsedSinceBootMillis(),
445                            settings.networkList, eventHandler);
446
447            } else {
448                Log.e(TAG, "Failed to start PNO scan");
449                reportPnoScanFailure();
450            }
451            return true;
452        }
453    }
454
455    @Override
456    public boolean resetHwPnoList() {
457        synchronized (mSettingsLock) {
458            if (mLastPnoScanSettings == null) {
459                Log.w(TAG, "No PNO scan running");
460                return false;
461            }
462            mLastPnoScanSettings = null;
463            // For wificond based PNO, we stop the scan immediately when we reset pno list.
464            stopHwPnoScan();
465            return true;
466        }
467    }
468
469    @Override
470    public boolean isHwPnoSupported(boolean isConnectedPno) {
471        // Hw Pno Scan is supported only for disconnected PNO when the device supports it.
472        return isHwPnoScanRequired(isConnectedPno);
473    }
474
475    @Override
476    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
477        synchronized (mSettingsLock) {
478            long nowMs = mClock.getElapsedSinceBootMillis();
479            pw.println("Latest native scan results:");
480            if (mNativeScanResults != null) {
481                List<ScanResult> scanResults = mNativeScanResults.stream().map(r -> {
482                    return r.getScanResult();
483                }).collect(Collectors.toList());
484                ScanResultUtil.dumpScanResults(pw, scanResults, nowMs);
485            }
486            pw.println("Latest native pno scan results:");
487            if (mNativePnoScanResults != null) {
488                List<ScanResult> pnoScanResults = mNativePnoScanResults.stream().map(r -> {
489                    return r.getScanResult();
490                }).collect(Collectors.toList());
491                ScanResultUtil.dumpScanResults(pw, pnoScanResults, nowMs);
492            }
493        }
494    }
495
496    private static class LastScanSettings {
497        LastScanSettings(long startTime,
498                boolean reportSingleScanFullResults,
499                ChannelCollection singleScanFreqs,
500                WifiNative.ScanEventHandler singleScanEventHandler) {
501            this.startTime = startTime;
502            this.reportSingleScanFullResults = reportSingleScanFullResults;
503            this.singleScanFreqs = singleScanFreqs;
504            this.singleScanEventHandler = singleScanEventHandler;
505        }
506
507        public long startTime;
508        public boolean reportSingleScanFullResults;
509        public ChannelCollection singleScanFreqs;
510        public WifiNative.ScanEventHandler singleScanEventHandler;
511
512    }
513
514    private static class LastPnoScanSettings {
515        LastPnoScanSettings(long startTime,
516                WifiNative.PnoNetwork[] pnoNetworkList,
517                WifiNative.PnoEventHandler pnoScanEventHandler) {
518            this.startTime = startTime;
519            this.pnoNetworkList = pnoNetworkList;
520            this.pnoScanEventHandler = pnoScanEventHandler;
521        }
522
523        public long startTime;
524        public WifiNative.PnoNetwork[] pnoNetworkList;
525        public WifiNative.PnoEventHandler pnoScanEventHandler;
526
527    }
528
529}
530