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.annotation.Nullable;
20import android.net.wifi.ScanResult;
21import android.net.wifi.WifiScanner.ScanData;
22import android.net.wifi.WifiScanner.ScanSettings;
23
24import com.android.server.wifi.WifiNative;
25
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * A class with utilities for dealing with scan schedules.
31 */
32public class ScanScheduleUtil {
33
34    /**
35     * Compares two ChannelSettings for equality.
36     */
37    public static boolean channelEquals(@Nullable WifiNative.ChannelSettings channel1,
38                                         @Nullable WifiNative.ChannelSettings channel2) {
39        if (channel1 == null || channel2 == null) return false;
40        if (channel1 == channel2) return true;
41
42        if (channel1.frequency != channel2.frequency) return false;
43        if (channel1.dwell_time_ms != channel2.dwell_time_ms) return false;
44        return channel1.passive == channel2.passive;
45    }
46
47    /**
48     * Compares two BucketSettings for equality.
49     */
50    public static boolean bucketEquals(@Nullable WifiNative.BucketSettings bucket1,
51                                        @Nullable WifiNative.BucketSettings bucket2) {
52        if (bucket1 == null || bucket2 == null) return false;
53        if (bucket1 == bucket2) return true;
54
55        if (bucket1.bucket != bucket2.bucket) return false;
56        if (bucket1.band != bucket2.band) return false;
57        if (bucket1.period_ms != bucket2.period_ms) return false;
58        if (bucket1.report_events != bucket2.report_events) return false;
59        if (bucket1.num_channels != bucket2.num_channels) return false;
60        for (int c = 0; c < bucket1.num_channels; c++) {
61            if (!channelEquals(bucket1.channels[c], bucket2.channels[c])) {
62                return false;
63            }
64        }
65
66        return true;
67    }
68
69    /**
70     * Compares two ScanSettings for equality.
71     */
72    public static boolean scheduleEquals(@Nullable WifiNative.ScanSettings schedule1,
73                                         @Nullable WifiNative.ScanSettings schedule2) {
74        if (schedule1 == null || schedule2 == null) return false;
75        if (schedule1 == schedule2) return true;
76
77        if (schedule1.base_period_ms != schedule2.base_period_ms) return false;
78        if (schedule1.max_ap_per_scan != schedule2.max_ap_per_scan) return false;
79        if (schedule1.report_threshold_percent != schedule2.report_threshold_percent) return false;
80        if (schedule1.report_threshold_num_scans != schedule2.report_threshold_num_scans) {
81            return false;
82        }
83        if (schedule1.num_buckets != schedule2.num_buckets) return false;
84        for (int b = 0; b < schedule1.num_buckets; b++) {
85            if (!bucketEquals(schedule1.buckets[b], schedule2.buckets[b])) {
86                return false;
87            }
88        }
89
90        return true;
91    }
92
93    /**
94     * Check if the specified bucket was scanned. If not all information is available then this
95     * method will return true.
96     *
97     * @param scheduledBucket Index of the bucket to check for, zero indexed, or -1 if any scan
98     *                        should be treated as scanning this bucket.
99     * @param bucketsScannedBitSet The bitset of all buckets scanned, 0 if unavailable
100     */
101    private static boolean isBucketMaybeScanned(int scheduledBucket, int bucketsScannedBitSet) {
102        if (bucketsScannedBitSet == 0 || scheduledBucket < 0) {
103            return true;
104        } else {
105            return (bucketsScannedBitSet & (1 << scheduledBucket)) != 0;
106        }
107    }
108
109    /**
110     * Check if the specified bucket was scanned. If not all information is available then this
111     * method will return false.
112     *
113     * @param scheduledBucket Index of the bucket to check for, zero indexed, or -1 if any scan
114     *                        should be treated as scanning this bucket.
115     * @param bucketsScannedBitSet The bitset of all buckets scanned, 0 if unavailable
116     */
117    private static boolean isBucketDefinitlyScanned(int scheduledBucket, int bucketsScannedBitSet) {
118        if (scheduledBucket < 0) {
119            return true;
120        } else if (bucketsScannedBitSet == 0) {
121            return false;
122        } else {
123            return (bucketsScannedBitSet & (1 << scheduledBucket)) != 0;
124        }
125    }
126
127    /**
128     * Returns true if the given scan result should be reported to a listener with the given
129     * settings.
130     */
131    public static boolean shouldReportFullScanResultForSettings(ChannelHelper channelHelper,
132            ScanResult result, int bucketsScanned, ScanSettings settings, int scheduledBucket) {
133        if (isBucketMaybeScanned(scheduledBucket, bucketsScanned)) {
134            return channelHelper.settingsContainChannel(settings, result.frequency);
135        } else {
136            return false;
137        }
138    }
139
140    /**
141     * Returns a filtered version of the scan results from the chip that represents only the data
142     * requested in the settings. Will return null if the result should not be reported.
143     *
144     * If a ScanData indicates that the bucket the settings were placed in was scanned then it
145     * will always be included (filtered to only include requested channels). If it indicates that
146     * the bucket was definitely not scanned then the scan data will not be reported.
147     * If it is not possible to determine if the settings bucket was scanned or not then a
148     * ScanData will be included if the scan was empty or there was at least one scan result that
149     * matches a requested channel (again the results will be filtered to only include requested
150     * channels.
151     */
152    public static ScanData[] filterResultsForSettings(ChannelHelper channelHelper,
153            ScanData[] scanDatas, ScanSettings settings, int scheduledBucket) {
154        List<ScanData> filteredScanDatas = new ArrayList<>(scanDatas.length);
155        List<ScanResult> filteredResults = new ArrayList<>();
156        for (ScanData scanData : scanDatas) {
157            // only report ScanData if the settings bucket could have been scanned
158            if (isBucketMaybeScanned(scheduledBucket, scanData.getBucketsScanned())) {
159                filteredResults.clear();
160                for (ScanResult scanResult : scanData.getResults()) {
161                    if (channelHelper.settingsContainChannel(settings, scanResult.frequency)) {
162                        filteredResults.add(scanResult);
163                    }
164                    if (settings.numBssidsPerScan > 0
165                            && filteredResults.size() >= settings.numBssidsPerScan) {
166                        break;
167                    }
168                }
169                // will include scan results if the scan was empty, there was at least one
170                // one result that matched the scan request or we are sure that all the requested
171                // channels were scanned.
172                if (filteredResults.size() == scanData.getResults().length) {
173                    filteredScanDatas.add(scanData);
174                } else if (filteredResults.size() > 0 || isBucketDefinitlyScanned(scheduledBucket,
175                                scanData.getBucketsScanned())) {
176                    filteredScanDatas.add(new ScanData(scanData.getId(),
177                                    scanData.getFlags(),
178                                    filteredResults.toArray(
179                                            new ScanResult[filteredResults.size()])));
180                }
181            }
182        }
183        if (filteredScanDatas.size() == 0) {
184            return null;
185        } else {
186            return filteredScanDatas.toArray(new ScanData[filteredScanDatas.size()]);
187        }
188    }
189}
190