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 static com.android.server.wifi.ScanTestUtil.NativeScanSettingsBuilder;
20import static com.android.server.wifi.ScanTestUtil.assertNativeScanSettingsEquals;
21import static com.android.server.wifi.ScanTestUtil.channelsToSpec;
22import static com.android.server.wifi.ScanTestUtil.createRequest;
23
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertNotNull;
26import static org.junit.Assert.assertTrue;
27import static org.mockito.Mockito.validateMockitoUsage;
28
29import android.net.wifi.WifiScanner;
30import android.net.wifi.WifiScanner.ScanSettings;
31import android.support.test.filters.SmallTest;
32import android.util.ArraySet;
33
34import com.android.server.wifi.WifiNative;
35import com.android.server.wifi.WifiNative.BucketSettings;
36import com.android.server.wifi.scanner.KnownBandsChannelHelper.KnownBandsChannelCollection;
37
38import org.junit.After;
39import org.junit.Before;
40import org.junit.Test;
41
42import java.lang.reflect.Field;
43import java.util.ArrayList;
44import java.util.Collection;
45import java.util.Collections;
46import java.util.Set;
47
48/**
49 * Unit tests for {@link com.android.server.wifi.scanner.BackgroundScanScheduler}.
50 */
51@SmallTest
52public class BackgroundScanSchedulerTest {
53
54    private static final int DEFAULT_MAX_BUCKETS = 9;
55    private static final int DEFAULT_MAX_CHANNELS_PER_BUCKET = 23;
56    private static final int DEFAULT_MAX_BATCH = 11;
57    private static final int DEFAULT_MAX_AP_PER_SCAN = 33;
58
59    private KnownBandsChannelHelper mChannelHelper;
60    private BackgroundScanScheduler mScheduler;
61
62    @Before
63    public void setUp() throws Exception {
64        mChannelHelper = new PresetKnownBandsChannelHelper(
65                new int[]{2400, 2450},
66                new int[]{5150, 5175},
67                new int[]{5600, 5650, 5660});
68        mScheduler = new BackgroundScanScheduler(mChannelHelper);
69        mScheduler.setMaxBuckets(DEFAULT_MAX_BUCKETS);
70        mScheduler.setMaxChannelsPerBucket(DEFAULT_MAX_CHANNELS_PER_BUCKET);
71        mScheduler.setMaxBatch(DEFAULT_MAX_BATCH);
72        mScheduler.setMaxApPerScan(DEFAULT_MAX_AP_PER_SCAN);
73    }
74
75    @After
76    public void cleanup() {
77        validateMockitoUsage();
78    }
79
80    @Test
81    public void noRequest() {
82        Collection<ScanSettings> requests = Collections.emptyList();
83
84        mScheduler.updateSchedule(requests);
85        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
86
87        assertEquals(30000, schedule.base_period_ms);
88        assertBuckets(schedule, 0);
89    }
90
91    @Test
92    public void singleRequest() {
93        Collection<ScanSettings> requests = Collections.singleton(createRequest(
94                WifiScanner.WIFI_BAND_BOTH, 30000, 0, 20,
95                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
96        ));
97
98        mScheduler.updateSchedule(requests);
99        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
100
101        assertEquals(30000, schedule.base_period_ms);
102        assertBuckets(schedule, 1);
103        for (ScanSettings request : requests) {
104            assertSettingsSatisfied(schedule, request, false, true);
105        }
106    }
107
108    @Test
109    public void singleRequestWithoutPredefinedBucket() {
110        Collection<ScanSettings> requests = Collections.singleton(createRequest(
111                WifiScanner.WIFI_BAND_BOTH, 7500, 0, 20,
112                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
113        ));
114
115        mScheduler.updateSchedule(requests);
116        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
117
118        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
119        assertBuckets(schedule, 1);
120        for (ScanSettings request : requests) {
121            assertSettingsSatisfied(schedule, request, false, true);
122        }
123    }
124
125    @Test
126    public void fewRequests() {
127        Collection<ScanSettings> requests = new ArrayList<>();
128        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH, 30000, 0, 20,
129                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
130        requests.add(createRequest(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY, 14000, 0, 20,
131                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
132
133        mScheduler.updateSchedule(requests);
134        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
135
136        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
137        assertBuckets(schedule, 2);
138        for (ScanSettings request : requests) {
139            assertSettingsSatisfied(schedule, request, false, true);
140        }
141    }
142
143    @Test
144    public void manyRequests() {
145        Collection<ScanSettings> requests = new ArrayList<>();
146        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH, 30000, 0, 20,
147                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
148        requests.add(createRequest(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY, 15000, 0, 20,
149                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
150        requests.add(createRequest(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY, 10000, 0, 20,
151                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
152
153        mScheduler.updateSchedule(requests);
154        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
155
156        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
157        assertBuckets(schedule, 2);
158        for (ScanSettings request : requests) {
159            assertSettingsSatisfied(schedule, request, false, false);
160        }
161    }
162
163    @Test
164    public void requestsWithNoPeriodCommonDenominator() {
165        ArrayList<ScanSettings> requests = new ArrayList<>();
166        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH, 299999, 0, 20,
167                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
168        requests.add(createRequest(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY, 10500, 0, 20,
169                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
170
171        mScheduler.updateSchedule(requests);
172        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
173
174        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
175        assertBuckets(schedule, 2);
176        for (ScanSettings request : requests) {
177            assertSettingsSatisfied(schedule, request, false, true);
178        }
179    }
180
181    @Test
182    public void manyRequestsDifferentReportScans() {
183        Collection<ScanSettings> requests = new ArrayList<>();
184        requests.add(createRequest(channelsToSpec(5175), 60000, 0, 20,
185                WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL));
186        requests.add(createRequest(channelsToSpec(2400), 60000, 0, 20,
187                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
188        requests.add(createRequest(channelsToSpec(2450), 60000, 0, 20,
189                WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
190        requests.add(createRequest(channelsToSpec(5150), 60000, 0, 20,
191                WifiScanner.REPORT_EVENT_NO_BATCH));
192
193        mScheduler.updateSchedule(requests);
194        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
195
196        assertEquals("base_period_ms", 60000, schedule.base_period_ms);
197        assertBuckets(schedule, 1);
198        for (ScanSettings request : requests) {
199            assertSettingsSatisfied(schedule, request, false, true);
200        }
201    }
202
203    @Test
204    public void exceedMaxBatch() {
205        Collection<ScanSettings> requests = new ArrayList<>();
206        requests.add(createRequest(channelsToSpec(5175), 30000, 10, 20,
207                WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL));
208
209        mScheduler.setMaxBatch(5);
210        mScheduler.updateSchedule(requests);
211        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
212
213        assertEquals("base_period_ms", 30000, schedule.base_period_ms);
214        assertBuckets(schedule, 1);
215        for (ScanSettings request : requests) {
216            assertSettingsSatisfied(schedule, request, false, true);
217        }
218        assertEquals("maxScansToCache", 5, schedule.report_threshold_num_scans);
219    }
220
221    @Test
222    public void defaultMaxBatch() {
223        Collection<ScanSettings> requests = new ArrayList<>();
224        requests.add(createRequest(channelsToSpec(5175), 60000, 0, 20,
225                WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL));
226
227        mScheduler.setMaxBatch(6);
228        mScheduler.updateSchedule(requests);
229        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
230
231        assertEquals("base_period_ms", 60000, schedule.base_period_ms);
232        assertBuckets(schedule, 1);
233        for (ScanSettings request : requests) {
234            assertSettingsSatisfied(schedule, request, false, true);
235        }
236        assertEquals("maxScansToCache", 6, schedule.report_threshold_num_scans);
237    }
238
239    @Test
240    public void exceedMaxAps() {
241        Collection<ScanSettings> requests = new ArrayList<>();
242        requests.add(createRequest(channelsToSpec(5175), 30000, 10, 20,
243                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
244
245        mScheduler.setMaxApPerScan(5);
246        mScheduler.updateSchedule(requests);
247        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
248
249        assertEquals("maxScansToCache", 5, schedule.max_ap_per_scan);
250    }
251
252    @Test
253    public void defaultMaxAps() {
254        Collection<ScanSettings> requests = new ArrayList<>();
255        requests.add(createRequest(channelsToSpec(5175), 30000, 10, 0,
256                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
257
258        mScheduler.setMaxApPerScan(8);
259        mScheduler.updateSchedule(requests);
260        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
261
262        assertEquals("maxApsPerScan", 8, schedule.max_ap_per_scan);
263    }
264
265    @Test
266    public void optimalScheduleExceedsNumberOfAvailableBuckets() {
267        ArrayList<ScanSettings> requests = new ArrayList<>();
268        requests.add(createRequest(channelsToSpec(2400), 30000, 0, 20,
269                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
270        requests.add(createRequest(channelsToSpec(2450), 10000, 0, 20,
271                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
272        requests.add(createRequest(channelsToSpec(5150), 120000, 0, 20,
273                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
274
275        mScheduler.setMaxBuckets(2);
276        mScheduler.updateSchedule(requests);
277        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
278
279        assertEquals("base_period_ms", 30000, schedule.base_period_ms);
280        assertBuckets(schedule, 2);
281        for (ScanSettings request : requests) {
282            assertSettingsSatisfied(schedule, request, true, true);
283        }
284    }
285
286    @Test
287    public void optimalScheduleExceedsNumberOfAvailableBuckets2() {
288        ArrayList<ScanSettings> requests = new ArrayList<>();
289        requests.add(createRequest(channelsToSpec(2400), 30000, 0, 20,
290                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
291        requests.add(createRequest(channelsToSpec(2450), 60000, 0, 20,
292                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
293        requests.add(createRequest(channelsToSpec(5150), 3840000, 0, 20,
294                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
295
296        mScheduler.setMaxBuckets(2);
297        mScheduler.updateSchedule(requests);
298        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
299
300        assertEquals("base_period_ms", 30000, schedule.base_period_ms);
301        assertBuckets(schedule, 2);
302        for (ScanSettings request : requests) {
303            assertSettingsSatisfied(schedule, request, true, true);
304        }
305    }
306
307    /**
308     * Ensure that a channel request is placed in the bucket closest to the original
309     * period and not the bucket it is initially placed in. Here the 5 min period is
310     * initially placed in the 240s bucket, but that bucket is eliminated because it
311     * would be a 7th bucket. This test ensures that the request is placed in the 480s
312     * bucket and not the 120s bucket.
313     */
314    @Test
315    public void optimalScheduleExceedsNumberOfAvailableBucketsClosestToOriginal() {
316        ArrayList<ScanSettings> requests = new ArrayList<>();
317        requests.add(createRequest(channelsToSpec(2400), 30 * 1000, 0, 20,
318                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
319        requests.add(createRequest(channelsToSpec(2450), 120 * 1000, 0, 20,
320                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
321        requests.add(createRequest(channelsToSpec(5150), 480 * 1000, 0, 20,
322                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
323        requests.add(createRequest(channelsToSpec(5175), 10 * 1000, 0, 20,
324                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
325        requests.add(createRequest(channelsToSpec(5600), 60 * 1000, 0, 20,
326                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
327        requests.add(createRequest(channelsToSpec(5650), 1920 * 1000, 0, 20,
328                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
329
330        requests.add(createRequest(channelsToSpec(5660), 300 * 1000, 0, 20, // 5 min
331                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
332
333        mScheduler.setMaxBuckets(6);
334        mScheduler.updateSchedule(requests);
335        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
336
337        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
338        assertBuckets(schedule, 6);
339        for (ScanSettings request : requests) {
340            assertSettingsSatisfied(schedule, request, true, true);
341        }
342    }
343
344    @Test
345    public void optimalScheduleExceedsMaxChannelsOnSingleBand() {
346        ArrayList<ScanSettings> requests = new ArrayList<>();
347        requests.add(createRequest(channelsToSpec(2400, 2450), 30000, 0, 20,
348                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
349
350        mScheduler.setMaxBuckets(2);
351        mScheduler.setMaxChannelsPerBucket(1);
352        mScheduler.updateSchedule(requests);
353        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
354
355        assertEquals("base_period_ms", 30000, schedule.base_period_ms);
356        assertBuckets(schedule, 2);
357        for (ScanSettings request : requests) {
358            assertSettingsSatisfied(schedule, request, true, true);
359        }
360    }
361
362    @Test
363    public void optimalScheduleExceedsMaxChannelsOnMultipleBands() {
364        ArrayList<ScanSettings> requests = new ArrayList<>();
365        requests.add(createRequest(channelsToSpec(2400, 2450, 5150), 30000, 0, 20,
366                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
367
368        mScheduler.setMaxBuckets(2);
369        mScheduler.setMaxChannelsPerBucket(2);
370        mScheduler.updateSchedule(requests);
371        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
372
373        assertEquals("base_period_ms", 30000, schedule.base_period_ms);
374        assertBuckets(schedule, 2);
375        for (ScanSettings request : requests) {
376            assertSettingsSatisfied(schedule, request, true, true);
377        }
378    }
379
380    @Test
381    public void optimalScheduleExceedsMaxChannelsOnMultipleBandsFromMultipleRequests() {
382        ArrayList<ScanSettings> requests = new ArrayList<>();
383        requests.add(createRequest(channelsToSpec(2400, 2450), 30000, 0, 20,
384                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
385        requests.add(createRequest(WifiScanner.WIFI_BAND_5_GHZ, 30000, 0, 20,
386                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
387
388        mScheduler.setMaxBuckets(2);
389        mScheduler.setMaxChannelsPerBucket(2);
390        mScheduler.updateSchedule(requests);
391        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
392
393        assertEquals("base_period_ms", 30000, schedule.base_period_ms);
394        assertBuckets(schedule, 2);
395        for (ScanSettings request : requests) {
396            assertSettingsSatisfied(schedule, request, true, true);
397        }
398    }
399
400    @Test
401    public void exactRequests() {
402        scheduleAndTestExactRequest(createRequest(WifiScanner.WIFI_BAND_BOTH, 30000, 0,
403                20, WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL));
404        scheduleAndTestExactRequest(createRequest(WifiScanner.WIFI_BAND_5_GHZ, 60000, 3,
405                13, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
406        scheduleAndTestExactRequest(createRequest(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY, 10000, 2,
407                10, WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT));
408        scheduleAndTestExactRequest(createRequest(WifiScanner.WIFI_BAND_BOTH, 25000, 0,
409                10, WifiScanner.REPORT_EVENT_NO_BATCH));
410        scheduleAndTestExactRequest(createRequest(WifiScanner.WIFI_BAND_BOTH, 25000, 3,
411                0, WifiScanner.REPORT_EVENT_NO_BATCH));
412        scheduleAndTestExactRequest(createRequest(channelsToSpec(2400, 5175, 5650) , 25000, 3,
413                0, WifiScanner.REPORT_EVENT_NO_BATCH));
414    }
415
416    @Test
417    public void singleExponentialBackOffRequest() {
418        Collection<ScanSettings> requests = Collections.singleton(createRequest(
419                WifiScanner.TYPE_LOW_LATENCY, WifiScanner.WIFI_BAND_BOTH, 30000, 160000, 2, 0, 20,
420                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
421        ));
422
423        mScheduler.updateSchedule(requests);
424        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
425
426        assertEquals(30000, schedule.base_period_ms);
427        assertBuckets(schedule, 1);
428        for (ScanSettings request : requests) {
429            assertSettingsSatisfied(schedule, request, false, true);
430        }
431    }
432
433    @Test
434    public void exponentialBackOffAndRegularRequests() {
435        Collection<ScanSettings> requests = new ArrayList<>();
436        requests.add(createRequest(WifiScanner.TYPE_LOW_LATENCY, WifiScanner.WIFI_BAND_BOTH, 30000,
437                200000, 1, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
438        requests.add(createRequest(channelsToSpec(5175), 30000, 0, 20,
439                WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL));
440
441        mScheduler.updateSchedule(requests);
442        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
443
444        assertEquals("base_period_ms", 30000, schedule.base_period_ms);
445        assertBuckets(schedule, 2);
446        for (ScanSettings request : requests) {
447            assertSettingsSatisfied(schedule, request, false, true);
448        }
449    }
450
451    /**
452     * Add 2 background scan requests with different time intervals, but one of the setting channels
453     * is totally contained in the other setting. Ensure that the requests are collapsed into a
454     * common bucket with the lower time period setting.
455     */
456    @Test
457    public void optimalScheduleFullyCollapsesDuplicateChannelsInBand() {
458        ArrayList<ScanSettings> requests = new ArrayList<>();
459        requests.add(createRequest(channelsToSpec(2400, 2450), 240000, 0, 20,
460                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
461        requests.add(createRequest(WifiScanner.WIFI_BAND_24_GHZ, 10000, 0, 20,
462                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
463
464        mScheduler.setMaxBuckets(2);
465        mScheduler.setMaxChannelsPerBucket(2);
466        mScheduler.updateSchedule(requests);
467        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
468
469        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
470        assertBuckets(schedule, 1);
471        for (ScanSettings request : requests) {
472            assertSettingsSatisfied(schedule, request, false, false);
473        }
474
475        assertEquals("scheduled bucket", 0, mScheduler.getScheduledBucket(requests.get(0)));
476        assertEquals("scheduled bucket", 0, mScheduler.getScheduledBucket(requests.get(1)));
477
478        KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
479        collection.addBand(WifiScanner.WIFI_BAND_24_GHZ);
480        Set<Integer> expectedBucketChannelSet = collection.getAllChannels();
481        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
482    }
483
484    /**
485     * Add 2 background scan requests with different time intervals, but one of the setting channels
486     * is totally contained in the other setting. Ensure that the requests are collapsed into a
487     * common bucket with the lower time period setting.
488     */
489    @Test
490    public void optimalScheduleFullyCollapsesDuplicateChannels() {
491        ArrayList<ScanSettings> requests = new ArrayList<>();
492        requests.add(createRequest(channelsToSpec(2400, 2450), 240000, 0, 20,
493                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
494        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
495                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
496
497        mScheduler.setMaxBuckets(2);
498        mScheduler.setMaxChannelsPerBucket(2);
499        mScheduler.updateSchedule(requests);
500        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
501
502        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
503        assertBuckets(schedule, 1);
504        for (ScanSettings request : requests) {
505            assertSettingsSatisfied(schedule, request, false, false);
506        }
507
508        assertEquals("scheduled bucket", 0, mScheduler.getScheduledBucket(requests.get(0)));
509        assertEquals("scheduled bucket", 0, mScheduler.getScheduledBucket(requests.get(1)));
510
511        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
512        expectedBucketChannelSet.add(2400);
513        expectedBucketChannelSet.add(2450);
514        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
515    }
516
517    /**
518     * Add 2 background scan requests with different time intervals, but one of the setting channels
519     * is partially contained in the other setting. Ensure that the requests are partially split
520     * across the lower time period bucket.
521     */
522    @Test
523    public void optimalSchedulePartiallyCollapsesDuplicateChannels() {
524        ArrayList<ScanSettings> requests = new ArrayList<>();
525        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
526                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
527        requests.add(createRequest(channelsToSpec(2400, 2450, 5175), 240000, 0, 20,
528                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
529
530        mScheduler.setMaxBuckets(2);
531        mScheduler.setMaxChannelsPerBucket(2);
532        mScheduler.updateSchedule(requests);
533        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
534
535        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
536        assertBuckets(schedule, 2);
537        for (ScanSettings request : requests) {
538            assertSettingsSatisfied(schedule, request, false, false);
539        }
540
541        assertEquals("scheduled bucket", 0, mScheduler.getScheduledBucket(requests.get(0)));
542        assertEquals("scheduled bucket", 1, mScheduler.getScheduledBucket(requests.get(1)));
543
544        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
545        expectedBucketChannelSet.add(2400);
546        expectedBucketChannelSet.add(2450);
547        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
548
549        expectedBucketChannelSet.clear();
550        expectedBucketChannelSet.add(5175);
551        assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
552    }
553
554    /**
555     * Add 2 background scan requests with different time intervals, but one of the setting channels
556     * is partially contained in the 2 other settings. Ensure that the requests are partially split
557     * across the lower time period buckets.
558     */
559    @Test
560    public void optimalSchedulePartiallyCollapsesDuplicateChannelsAcrossMultipleBuckets() {
561        ArrayList<ScanSettings> requests = new ArrayList<>();
562        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
563                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
564        requests.add(createRequest(channelsToSpec(2400, 2450, 5175), 30000, 0, 20,
565                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
566        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 240000, 0, 20,
567                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
568
569        mScheduler.setMaxBuckets(3);
570        mScheduler.updateSchedule(requests);
571        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
572
573        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
574        assertBuckets(schedule, 3);
575        for (ScanSettings request : requests) {
576            assertSettingsSatisfied(schedule, request, false, false);
577        }
578
579        assertEquals("scheduled bucket", 0, mScheduler.getScheduledBucket(requests.get(0)));
580        assertEquals("scheduled bucket", 1, mScheduler.getScheduledBucket(requests.get(1)));
581        assertEquals("scheduled bucket", 2, mScheduler.getScheduledBucket(requests.get(2)));
582
583        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
584        expectedBucketChannelSet.add(2400);
585        expectedBucketChannelSet.add(2450);
586        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
587
588        expectedBucketChannelSet.clear();
589        expectedBucketChannelSet.add(5175);
590        assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
591
592        KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
593        collection.addBand(WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
594        expectedBucketChannelSet = collection.getAllChannels();
595        expectedBucketChannelSet.remove(5175);
596        expectedBucketChannelSet.remove(2400);
597        expectedBucketChannelSet.remove(2450);
598        assertBucketChannels(schedule.buckets[2], expectedBucketChannelSet);
599    }
600
601    /**
602     * Add 2 background scan requests with different time intervals, but one of the setting channels
603     * is partially contained in the 2 other settings. Ensure that the requests are partially split
604     * across the lower time period buckets and the last bucket is split into 2 because the
605     * channel list does not fit into a single bucket.
606     */
607    @Test
608    public void optimalSchedulePartiallyCollapsesDuplicateChannelsWithSplitBuckets() {
609        ArrayList<ScanSettings> requests = new ArrayList<>();
610        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
611                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
612        requests.add(createRequest(channelsToSpec(2400, 2450, 5175), 30000, 0, 20,
613                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
614        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 240000, 0, 20,
615                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
616
617        mScheduler.setMaxBuckets(5);
618        mScheduler.setMaxChannelsPerBucket(2);
619        mScheduler.updateSchedule(requests);
620        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
621
622        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
623        assertBuckets(schedule, 4);
624        for (ScanSettings request : requests) {
625            assertSettingsSatisfied(schedule, request, false, false);
626        }
627
628        assertEquals("scheduled bucket", 0, mScheduler.getScheduledBucket(requests.get(0)));
629        assertEquals("scheduled bucket", 1, mScheduler.getScheduledBucket(requests.get(1)));
630        assertEquals("scheduled bucket", 2, mScheduler.getScheduledBucket(requests.get(2)));
631
632        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
633        expectedBucketChannelSet.add(2400);
634        expectedBucketChannelSet.add(2450);
635        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
636
637        expectedBucketChannelSet.clear();
638        expectedBucketChannelSet.add(5175);
639        assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
640
641        KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
642        collection.addBand(WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
643        expectedBucketChannelSet = collection.getAllChannels();
644        expectedBucketChannelSet.remove(5175);
645        expectedBucketChannelSet.remove(2400);
646        expectedBucketChannelSet.remove(2450);
647        // Check if the combined channel set matches what we expect
648        Set<Integer> combinedBucketChannelSet = getAllChannels(schedule.buckets[2]);
649        combinedBucketChannelSet.addAll(getAllChannels(schedule.buckets[3]));
650        assertChannels(combinedBucketChannelSet, expectedBucketChannelSet);
651    }
652
653    protected Set<Integer> getAllChannels(BucketSettings bucket) {
654        KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
655        collection.addChannels(bucket);
656        return collection.getAllChannels();
657    }
658
659    protected Set<Integer> getAllChannels(WifiScanner.ScanSettings settings) {
660        KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
661        collection.addChannels(settings);
662        return collection.getAllChannels();
663    }
664
665    public void scheduleAndTestExactRequest(ScanSettings settings) {
666        Collection<ScanSettings> requests = new ArrayList<>();
667        requests.add(settings);
668
669        mScheduler.updateSchedule(requests);
670        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
671
672        int expectedPeriod = computeExpectedPeriod(settings.periodInMs);
673        NativeScanSettingsBuilder expectedBuilder = new NativeScanSettingsBuilder()
674                .withBasePeriod(expectedPeriod)
675                .withMaxApPerScan(settings.numBssidsPerScan == 0
676                        ? DEFAULT_MAX_AP_PER_SCAN
677                        : settings.numBssidsPerScan)
678                .withMaxScansToCache(settings.maxScansToCache == 0
679                        ? DEFAULT_MAX_BATCH
680                        : settings.maxScansToCache);
681
682        if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
683            expectedBuilder.addBucketWithChannels(expectedPeriod, settings.reportEvents,
684                    settings.channels);
685        } else {
686            expectedBuilder.addBucketWithBand(expectedPeriod, settings.reportEvents, settings.band);
687        }
688        assertNativeScanSettingsEquals(expectedBuilder.build(), schedule);
689    }
690
691    private void assertBuckets(WifiNative.ScanSettings schedule, int numBuckets) {
692        assertEquals("num_buckets", numBuckets, schedule.num_buckets);
693        assertNotNull("buckets was null", schedule.buckets);
694        assertEquals("num_buckets and actual buckets", schedule.num_buckets,
695                schedule.buckets.length);
696        for (int i = 0; i < numBuckets; i++) {
697            assertNotNull("bucket[" + i + "] was null", schedule.buckets[i]);
698            if (schedule.buckets[i].band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
699                assertTrue("num channels <= 0", schedule.buckets[i].num_channels > 0);
700                assertTrue("bucket channels > max channels",
701                        schedule.buckets[i].num_channels <= mScheduler.getMaxChannelsPerBucket());
702                assertNotNull("Channels was null", schedule.buckets[i].channels);
703                for (int c = 0; c < schedule.buckets[i].num_channels; c++) {
704                    assertNotNull("Channel was null", schedule.buckets[i].channels[c]);
705                }
706            } else {
707                assertTrue("Invalid band: " + schedule.buckets[i].band,
708                        schedule.buckets[i].band > WifiScanner.WIFI_BAND_UNSPECIFIED
709                        && schedule.buckets[i].band <= WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
710            }
711        }
712    }
713
714    private void assertSettingsSatisfied(WifiNative.ScanSettings schedule,
715            ScanSettings settings, boolean bucketsLimited, boolean exactPeriod) {
716        assertTrue("bssids per scan: " + schedule.max_ap_per_scan + " /<= "
717                + settings.numBssidsPerScan,
718                schedule.max_ap_per_scan <= settings.numBssidsPerScan);
719
720        if (settings.maxScansToCache > 0) {
721            assertTrue("scans to cache: " + schedule.report_threshold_num_scans + " /<= "
722                    + settings.maxScansToCache,
723                    schedule.report_threshold_num_scans <= settings.maxScansToCache);
724        }
725
726        Set<Integer> channelSet = getAllChannels(settings);
727
728        StringBuilder ignoreString = new StringBuilder();
729
730        KnownBandsChannelCollection scheduleChannels = mChannelHelper.createChannelCollection();
731        for (int b = 0; b < schedule.num_buckets; b++) {
732            BucketSettings bucket = schedule.buckets[b];
733            if ((settings.reportEvents & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
734                if ((bucket.report_events & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) == 0) {
735                    ignoreString
736                            .append(" ")
737                            .append(getAllChannels(bucket))
738                            .append("=after_each_scan:")
739                            .append(bucket.report_events & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN)
740                            .append("!=")
741                            .append(settings.reportEvents
742                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
743                    continue;
744                }
745            }
746            if ((settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
747                if ((bucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) == 0) {
748                    ignoreString
749                            .append(" ")
750                            .append(getAllChannels(bucket))
751                            .append("=full_result:")
752                            .append(bucket.report_events
753                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
754                            .append("!=")
755                            .append(settings.reportEvents
756                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT);
757                    continue;
758                }
759            }
760            if ((settings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
761                if ((bucket.report_events & WifiScanner.REPORT_EVENT_NO_BATCH) != 0) {
762                    ignoreString
763                            .append(" ")
764                            .append(getAllChannels(bucket))
765                            .append("=no_batch:")
766                            .append(bucket.report_events & WifiScanner.REPORT_EVENT_NO_BATCH)
767                            .append("!=")
768                            .append(settings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH);
769                    continue;
770                }
771            }
772            int expectedPeriod;
773
774            if (settings.maxPeriodInMs != 0 && settings.periodInMs != settings.maxPeriodInMs) {
775                // exponential back off scan
776                expectedPeriod = settings.periodInMs;
777            } else {
778                if (bucketsLimited) {
779                    expectedPeriod = computeExpectedPeriod(settings.periodInMs, schedule);
780                } else {
781                    expectedPeriod = computeExpectedPeriod(settings.periodInMs);
782                }
783            }
784
785            if (exactPeriod) {
786                if (bucket.period_ms != expectedPeriod) {
787                    ignoreString
788                            .append(" ")
789                            .append(getAllChannels(bucket))
790                            .append("=period:")
791                            .append(bucket.period_ms)
792                            .append("!=")
793                            .append(settings.periodInMs);
794                    continue;
795                }
796            } else {
797                if (bucket.period_ms > expectedPeriod) {
798                    ignoreString
799                            .append(" ")
800                            .append(getAllChannels(bucket))
801                            .append("=period:")
802                            .append(bucket.period_ms)
803                            .append(">")
804                            .append(settings.periodInMs);
805                    continue;
806                }
807            }
808            scheduleChannels.addChannels(bucket);
809        }
810
811        assertTrue("expected that " + scheduleChannels.getAllChannels() + " contained "
812                + channelSet + ", Channel ignore reasons:" + ignoreString.toString(),
813                scheduleChannels.getAllChannels().containsAll(channelSet));
814    }
815
816    private void assertBucketChannels(BucketSettings bucket, Set<Integer> expectedChannelSet) {
817        Set<Integer> bucketChannelSet = getAllChannels(bucket);
818        assertChannels(bucketChannelSet, expectedChannelSet);
819    }
820
821    private void assertChannels(Set<Integer> channelSet, Set<Integer> expectedChannelSet) {
822        assertTrue("expected that " + channelSet + " contained "
823                + expectedChannelSet, channelSet.containsAll(expectedChannelSet));
824    }
825
826    private static int[] getPredefinedBuckets() {
827        try {
828            Field f = BackgroundScanScheduler.class.getDeclaredField("PREDEFINED_BUCKET_PERIODS");
829            f.setAccessible(true);
830            return (int[]) f.get(null);
831        } catch (Exception e) {
832            throw new RuntimeException("Could not get predefined buckets", e);
833        }
834    }
835    private static final int[] PREDEFINED_BUCKET_PERIODS = getPredefinedBuckets();
836
837    // find closest bucket period to the requested period
838    private static int computeExpectedPeriod(int requestedPeriod) {
839        int period = 0;
840        int minDiff = Integer.MAX_VALUE;
841        for (int bucketPeriod : PREDEFINED_BUCKET_PERIODS) {
842            int diff = Math.abs(bucketPeriod - requestedPeriod);
843            if (diff < minDiff) {
844                minDiff = diff;
845                period = bucketPeriod;
846            }
847        }
848        return period;
849    }
850
851    // find closest bucket period to the requested period that exists in the schedule
852    private static int computeExpectedPeriod(int requestedPeriod,
853            WifiNative.ScanSettings schedule) {
854        int period = 0;
855        int minDiff = Integer.MAX_VALUE;
856        for (int i = 0; i < schedule.num_buckets; ++i) {
857            int bucketPeriod = schedule.buckets[i].period_ms;
858            int diff = Math.abs(bucketPeriod - requestedPeriod);
859            if (diff < minDiff) {
860                minDiff = diff;
861                period = bucketPeriod;
862            }
863        }
864        return period;
865    }
866}
867