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.test.suitebuilder.annotation.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.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.WIFI_BAND_BOTH, 30000, 200000, 1,
437                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    /**
654     * Add 2 background scan requests with different time intervals, but one of the setting channels
655     * is totally contained in the other setting. Ensure that the requests are collapsed into a
656     * common bucket with the lower time period setting.
657     * This is done with NoBandChannelHelper.
658     */
659    @Test
660    public void optimalScheduleFullyCollapsesDuplicateChannelsInBandWithNoBandChannelHelper() {
661        BackgroundScanScheduler scheduler = createSchedulerWithNoBandChannelHelper();
662
663        ArrayList<ScanSettings> requests = new ArrayList<>();
664        requests.add(createRequest(channelsToSpec(2400, 2450), 160000, 0, 20,
665                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
666        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 10000, 0, 20,
667                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
668
669        scheduler.setMaxBuckets(2);
670        scheduler.setMaxChannelsPerBucket(2);
671        scheduler.updateSchedule(requests);
672        WifiNative.ScanSettings schedule = scheduler.getSchedule();
673
674        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
675        assertBuckets(schedule, 1);
676
677        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(0)));
678        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(1)));
679
680        assertEquals("band", schedule.buckets[0].band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
681    }
682
683    /**
684     * Add 2 background scan requests with different time intervals, but one of the setting channels
685     * is partially contained in the other setting. Ensure that the requests are partially split
686     * across the lower time period bucket.
687     */
688    @Test
689    public void optimalSchedulePartiallyCollapsesDuplicateChannelsWithNoBandChannelHelper() {
690        BackgroundScanScheduler scheduler = createSchedulerWithNoBandChannelHelper();
691
692        ArrayList<ScanSettings> requests = new ArrayList<>();
693        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
694                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
695        requests.add(createRequest(channelsToSpec(2400, 2450, 5175), 240000, 0, 20,
696                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
697
698        scheduler.setMaxBuckets(2);
699        scheduler.setMaxChannelsPerBucket(3);
700        scheduler.updateSchedule(requests);
701        WifiNative.ScanSettings schedule = scheduler.getSchedule();
702
703        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
704        assertBuckets(schedule, 2);
705
706        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(0)));
707        assertEquals("scheduled bucket", 1, scheduler.getScheduledBucket(requests.get(1)));
708
709        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
710        expectedBucketChannelSet.add(2400);
711        expectedBucketChannelSet.add(2450);
712        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
713
714        expectedBucketChannelSet.clear();
715        expectedBucketChannelSet.add(5175);
716        assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
717    }
718
719    /**
720     * Add 2 background scan requests with the second scan request having channels more than the
721     * max, ensure that the last bucket is split.
722     */
723    @Test
724    public void optimalScheduleShouldSplitBucketsWithNoBandChannelHelper() {
725        BackgroundScanScheduler scheduler = createSchedulerWithNoBandChannelHelper();
726
727        ArrayList<ScanSettings> requests = new ArrayList<>();
728        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
729                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
730        requests.add(createRequest(channelsToSpec(5150, 5175, 5600, 5650), 240000, 0, 20,
731                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
732
733        scheduler.setMaxBuckets(3);
734        scheduler.setMaxChannelsPerBucket(2);
735        scheduler.updateSchedule(requests);
736        WifiNative.ScanSettings schedule = scheduler.getSchedule();
737
738        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
739        assertBuckets(schedule, 3);
740
741        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(0)));
742        assertEquals("scheduled bucket", 1, scheduler.getScheduledBucket(requests.get(1)));
743
744        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
745        expectedBucketChannelSet.add(2400);
746        expectedBucketChannelSet.add(2450);
747        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
748
749        expectedBucketChannelSet.clear();
750        expectedBucketChannelSet.add(5150);
751        expectedBucketChannelSet.add(5175);
752        assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
753
754        expectedBucketChannelSet.clear();
755        expectedBucketChannelSet.add(5600);
756        expectedBucketChannelSet.add(5650);
757        assertBucketChannels(schedule.buckets[2], expectedBucketChannelSet);
758    }
759
760    protected Set<Integer> getAllChannels(BucketSettings bucket) {
761        KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
762        collection.addChannels(bucket);
763        return collection.getAllChannels();
764    }
765
766    protected Set<Integer> getAllChannels(WifiScanner.ScanSettings settings) {
767        KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
768        collection.addChannels(settings);
769        return collection.getAllChannels();
770    }
771
772    public void scheduleAndTestExactRequest(ScanSettings settings) {
773        Collection<ScanSettings> requests = new ArrayList<>();
774        requests.add(settings);
775
776        mScheduler.updateSchedule(requests);
777        WifiNative.ScanSettings schedule = mScheduler.getSchedule();
778
779        int expectedPeriod = computeExpectedPeriod(settings.periodInMs);
780        NativeScanSettingsBuilder expectedBuilder = new NativeScanSettingsBuilder()
781                .withBasePeriod(expectedPeriod)
782                .withMaxApPerScan(settings.numBssidsPerScan == 0
783                        ? DEFAULT_MAX_AP_PER_SCAN
784                        : settings.numBssidsPerScan)
785                .withMaxScansToCache(settings.maxScansToCache == 0
786                        ? DEFAULT_MAX_BATCH
787                        : settings.maxScansToCache);
788
789        if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
790            expectedBuilder.addBucketWithChannels(expectedPeriod, settings.reportEvents,
791                    settings.channels);
792        } else {
793            expectedBuilder.addBucketWithBand(expectedPeriod, settings.reportEvents, settings.band);
794        }
795        assertNativeScanSettingsEquals(expectedBuilder.build(), schedule);
796    }
797
798    private void assertBuckets(WifiNative.ScanSettings schedule, int numBuckets) {
799        assertEquals("num_buckets", numBuckets, schedule.num_buckets);
800        assertNotNull("buckets was null", schedule.buckets);
801        assertEquals("num_buckets and actual buckets", schedule.num_buckets,
802                schedule.buckets.length);
803        for (int i = 0; i < numBuckets; i++) {
804            assertNotNull("bucket[" + i + "] was null", schedule.buckets[i]);
805            if (schedule.buckets[i].band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
806                assertTrue("num channels <= 0", schedule.buckets[i].num_channels > 0);
807                assertTrue("bucket channels > max channels",
808                        schedule.buckets[i].num_channels <= mScheduler.getMaxChannelsPerBucket());
809                assertNotNull("Channels was null", schedule.buckets[i].channels);
810                for (int c = 0; c < schedule.buckets[i].num_channels; c++) {
811                    assertNotNull("Channel was null", schedule.buckets[i].channels[c]);
812                }
813            } else {
814                assertTrue("Invalid band: " + schedule.buckets[i].band,
815                        schedule.buckets[i].band > WifiScanner.WIFI_BAND_UNSPECIFIED
816                        && schedule.buckets[i].band <= WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
817            }
818        }
819    }
820
821    private void assertSettingsSatisfied(WifiNative.ScanSettings schedule,
822            ScanSettings settings, boolean bucketsLimited, boolean exactPeriod) {
823        assertTrue("bssids per scan: " + schedule.max_ap_per_scan + " /<= "
824                + settings.numBssidsPerScan,
825                schedule.max_ap_per_scan <= settings.numBssidsPerScan);
826
827        if (settings.maxScansToCache > 0) {
828            assertTrue("scans to cache: " + schedule.report_threshold_num_scans + " /<= "
829                    + settings.maxScansToCache,
830                    schedule.report_threshold_num_scans <= settings.maxScansToCache);
831        }
832
833        Set<Integer> channelSet = getAllChannels(settings);
834
835        StringBuilder ignoreString = new StringBuilder();
836
837        KnownBandsChannelCollection scheduleChannels = mChannelHelper.createChannelCollection();
838        for (int b = 0; b < schedule.num_buckets; b++) {
839            BucketSettings bucket = schedule.buckets[b];
840            if ((settings.reportEvents & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
841                if ((bucket.report_events & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) == 0) {
842                    ignoreString
843                            .append(" ")
844                            .append(getAllChannels(bucket))
845                            .append("=after_each_scan:")
846                            .append(bucket.report_events & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN)
847                            .append("!=")
848                            .append(settings.reportEvents
849                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
850                    continue;
851                }
852            }
853            if ((settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
854                if ((bucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) == 0) {
855                    ignoreString
856                            .append(" ")
857                            .append(getAllChannels(bucket))
858                            .append("=full_result:")
859                            .append(bucket.report_events
860                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
861                            .append("!=")
862                            .append(settings.reportEvents
863                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT);
864                    continue;
865                }
866            }
867            if ((settings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
868                if ((bucket.report_events & WifiScanner.REPORT_EVENT_NO_BATCH) != 0) {
869                    ignoreString
870                            .append(" ")
871                            .append(getAllChannels(bucket))
872                            .append("=no_batch:")
873                            .append(bucket.report_events & WifiScanner.REPORT_EVENT_NO_BATCH)
874                            .append("!=")
875                            .append(settings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH);
876                    continue;
877                }
878            }
879            int expectedPeriod;
880
881            if (settings.maxPeriodInMs != 0 && settings.periodInMs != settings.maxPeriodInMs) {
882                // exponential back off scan
883                expectedPeriod = settings.periodInMs;
884            } else {
885                if (bucketsLimited) {
886                    expectedPeriod = computeExpectedPeriod(settings.periodInMs, schedule);
887                } else {
888                    expectedPeriod = computeExpectedPeriod(settings.periodInMs);
889                }
890            }
891
892            if (exactPeriod) {
893                if (bucket.period_ms != expectedPeriod) {
894                    ignoreString
895                            .append(" ")
896                            .append(getAllChannels(bucket))
897                            .append("=period:")
898                            .append(bucket.period_ms)
899                            .append("!=")
900                            .append(settings.periodInMs);
901                    continue;
902                }
903            } else {
904                if (bucket.period_ms > expectedPeriod) {
905                    ignoreString
906                            .append(" ")
907                            .append(getAllChannels(bucket))
908                            .append("=period:")
909                            .append(bucket.period_ms)
910                            .append(">")
911                            .append(settings.periodInMs);
912                    continue;
913                }
914            }
915            scheduleChannels.addChannels(bucket);
916        }
917
918        assertTrue("expected that " + scheduleChannels.getAllChannels() + " contained "
919                + channelSet + ", Channel ignore reasons:" + ignoreString.toString(),
920                scheduleChannels.getAllChannels().containsAll(channelSet));
921    }
922
923    private void assertBucketChannels(BucketSettings bucket, Set<Integer> expectedChannelSet) {
924        Set<Integer> bucketChannelSet = getAllChannels(bucket);
925        assertChannels(bucketChannelSet, expectedChannelSet);
926    }
927
928    private void assertChannels(Set<Integer> channelSet, Set<Integer> expectedChannelSet) {
929        assertTrue("expected that " + channelSet + " contained "
930                + expectedChannelSet, channelSet.containsAll(expectedChannelSet));
931    }
932
933    private BackgroundScanScheduler createSchedulerWithNoBandChannelHelper() {
934        NoBandChannelHelper channelHelper = new NoBandChannelHelper();
935        BackgroundScanScheduler scheduler = new BackgroundScanScheduler(channelHelper);
936        scheduler.setMaxBuckets(DEFAULT_MAX_BUCKETS);
937        scheduler.setMaxChannelsPerBucket(DEFAULT_MAX_CHANNELS_PER_BUCKET);
938        scheduler.setMaxBatch(DEFAULT_MAX_BATCH);
939        scheduler.setMaxApPerScan(DEFAULT_MAX_AP_PER_SCAN);
940        return scheduler;
941    }
942
943    private static int[] getPredefinedBuckets() {
944        try {
945            Field f = BackgroundScanScheduler.class.getDeclaredField("PREDEFINED_BUCKET_PERIODS");
946            f.setAccessible(true);
947            return (int[]) f.get(null);
948        } catch (Exception e) {
949            throw new RuntimeException("Could not get predefined buckets", e);
950        }
951    }
952    private static final int[] PREDEFINED_BUCKET_PERIODS = getPredefinedBuckets();
953
954    // find closest bucket period to the requested period
955    private static int computeExpectedPeriod(int requestedPeriod) {
956        int period = 0;
957        int minDiff = Integer.MAX_VALUE;
958        for (int bucketPeriod : PREDEFINED_BUCKET_PERIODS) {
959            int diff = Math.abs(bucketPeriod - requestedPeriod);
960            if (diff < minDiff) {
961                minDiff = diff;
962                period = bucketPeriod;
963            }
964        }
965        return period;
966    }
967
968    // find closest bucket period to the requested period that exists in the schedule
969    private static int computeExpectedPeriod(int requestedPeriod,
970            WifiNative.ScanSettings schedule) {
971        int period = 0;
972        int minDiff = Integer.MAX_VALUE;
973        for (int i = 0; i < schedule.num_buckets; ++i) {
974            int bucketPeriod = schedule.buckets[i].period_ms;
975            int diff = Math.abs(bucketPeriod - requestedPeriod);
976            if (diff < minDiff) {
977                minDiff = diff;
978                period = bucketPeriod;
979            }
980        }
981        return period;
982    }
983}
984