• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.settingslib.wifi;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.mockito.Mockito.any;
25 import static org.mockito.Mockito.anyInt;
26 import static org.mockito.Mockito.doAnswer;
27 import static org.mockito.Mockito.doNothing;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.spy;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.when;
34 
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.net.ConnectivityManager;
39 import android.net.Network;
40 import android.net.NetworkInfo;
41 import android.net.NetworkKey;
42 import android.net.NetworkScoreManager;
43 import android.net.RssiCurve;
44 import android.net.ScoredNetwork;
45 import android.net.WifiKey;
46 import android.net.wifi.ScanResult;
47 import android.net.wifi.WifiConfiguration;
48 import android.net.wifi.WifiInfo;
49 import android.net.wifi.WifiManager;
50 import android.net.wifi.WifiNetworkScoreCache;
51 import android.net.wifi.WifiSsid;
52 import android.net.wifi.hotspot2.OsuProvider;
53 import android.net.wifi.hotspot2.PasspointConfiguration;
54 import android.os.Bundle;
55 import android.os.Handler;
56 import android.os.HandlerThread;
57 import android.os.SystemClock;
58 import android.provider.Settings;
59 import android.util.ArraySet;
60 import android.util.Pair;
61 
62 import androidx.test.InstrumentationRegistry;
63 import androidx.test.filters.SmallTest;
64 import androidx.test.runner.AndroidJUnit4;
65 
66 import com.android.settingslib.utils.ThreadUtils;
67 
68 import org.junit.After;
69 import org.junit.Before;
70 import org.junit.Test;
71 import org.junit.runner.RunWith;
72 import org.mockito.ArgumentCaptor;
73 import org.mockito.Captor;
74 import org.mockito.Matchers;
75 import org.mockito.Mock;
76 import org.mockito.MockitoAnnotations;
77 import org.mockito.invocation.InvocationOnMock;
78 import org.mockito.stubbing.Answer;
79 
80 import java.util.ArrayList;
81 import java.util.Arrays;
82 import java.util.HashMap;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Set;
86 import java.util.concurrent.CountDownLatch;
87 import java.util.concurrent.TimeUnit;
88 import java.util.concurrent.atomic.AtomicBoolean;
89 
90 // TODO(sghuman): Change these to robolectric tests b/35766684.
91 @SmallTest
92 @RunWith(AndroidJUnit4.class)
93 public class WifiTrackerTest {
94 
95     private static final String TAG = "WifiTrackerTest";
96     private static final int LATCH_TIMEOUT = 4000;
97 
98     private static final String SSID_1 = "ssid1";
99     private static final String BSSID_1 = "00:00:00:00:00:00";
100     private static final NetworkKey NETWORK_KEY_1 =
101             new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1));
102     private static final int RSSI_1 = -30;
103     private static final byte SCORE_1 = 10;
104     private static final int BADGE_1 = AccessPoint.Speed.MODERATE;
105     private static final String FQDN_1 = "fqdn1";
106     private static final String PROVIDER_FRIENDLY_NAME_1 = "providerFriendlyName1";
107 
108     private static final String SSID_2 = "ssid2";
109     private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA";
110     private static final NetworkKey NETWORK_KEY_2 =
111             new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2));
112     private static final int RSSI_2 = -30;
113     private static final byte SCORE_2 = 15;
114     private static final int BADGE_2 = AccessPoint.Speed.FAST;
115     private static final String FQDN_2 = "fqdn2";
116     private static final String PROVIDER_FRIENDLY_NAME_2 = "providerFriendlyName2";
117 
118     private static final String SSID_3 = "ssid3";
119     private static final String BSSID_3 = "CC:00:00:00:00:00";
120     private static final int RSSI_3 = -40;
121 
122     // TODO(b/65594609): Convert mutable Data objects to instance variables / builder pattern
123     private static final int NETWORK_ID_1 = 123;
124     private static final int CONNECTED_RSSI = -50;
125     private static final WifiInfo CONNECTED_AP_1_INFO = new WifiInfo();
126     static {
WifiSsid.fromUtf8Text(SSID_1)127         CONNECTED_AP_1_INFO.setSSID(WifiSsid.fromUtf8Text(SSID_1));
128         CONNECTED_AP_1_INFO.setBSSID(BSSID_1);
129         CONNECTED_AP_1_INFO.setNetworkId(NETWORK_ID_1);
130         CONNECTED_AP_1_INFO.setRssi(CONNECTED_RSSI);
131     }
132     private static final WifiConfiguration CONFIGURATION_1 = new WifiConfiguration();
133     static {
134         CONFIGURATION_1.SSID = SSID_1;
135         CONFIGURATION_1.BSSID = BSSID_1;
136         CONFIGURATION_1.networkId = NETWORK_ID_1;
137     }
138 
139     private static final int NETWORK_ID_2 = 2;
140     private static final WifiConfiguration CONFIGURATION_2 = new WifiConfiguration();
141     static {
142         CONFIGURATION_2.SSID = SSID_2;
143         CONFIGURATION_2.BSSID = BSSID_2;
144         CONFIGURATION_2.networkId = NETWORK_ID_2;
145     }
146 
147     @Captor ArgumentCaptor<WifiNetworkScoreCache> mScoreCacheCaptor;
148     @Mock private ConnectivityManager mockConnectivityManager;
149     @Mock private NetworkScoreManager mockNetworkScoreManager;
150     @Mock private RssiCurve mockCurve1;
151     @Mock private RssiCurve mockCurve2;
152     @Mock private RssiCurve mockBadgeCurve1;
153     @Mock private RssiCurve mockBadgeCurve2;
154     @Mock private WifiManager mockWifiManager;
155     @Mock private WifiTracker.WifiListener mockWifiListener;
156 
157     private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
158 
159     private Context mContext;
160     private CountDownLatch mAccessPointsChangedLatch;
161     private CountDownLatch mRequestScoresLatch;
162     private Handler mScannerHandler;
163     private HandlerThread mWorkerThread;
164 
165     private int mOriginalScoringUiSettingValue;
166 
167     @SuppressWarnings("VisibleForTests")
168     @Before
setUp()169     public void setUp() {
170         MockitoAnnotations.initMocks(this);
171 
172         mContext = InstrumentationRegistry.getTargetContext();
173 
174         mWorkerThread = new HandlerThread("TestHandlerWorkerThread");
175         mWorkerThread.start();
176 
177         // Make sure the scanner doesn't try to run on the testing thread.
178         HandlerThread scannerThread = new HandlerThread("ScannerWorkerThread");
179         scannerThread.start();
180         mScannerHandler = new Handler(scannerThread.getLooper());
181 
182         when(mockWifiManager.isWifiEnabled()).thenReturn(true);
183         when(mockWifiManager.getScanResults())
184                 .thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2()));
185         when(mockWifiManager.getConfiguredNetworks())
186                 .thenReturn(Arrays.asList(CONFIGURATION_1, CONFIGURATION_2));
187 
188 
189         when(mockCurve1.lookupScore(RSSI_1)).thenReturn(SCORE_1);
190         when(mockCurve2.lookupScore(RSSI_2)).thenReturn(SCORE_2);
191 
192         when(mockBadgeCurve1.lookupScore(RSSI_1)).thenReturn((byte) BADGE_1);
193         when(mockBadgeCurve2.lookupScore(RSSI_2)).thenReturn((byte) BADGE_2);
194 
195         doNothing()
196                 .when(mockNetworkScoreManager)
197                 .registerNetworkScoreCache(
198                         anyInt(),
199                         mScoreCacheCaptor.capture(),
200                         Matchers.anyInt());
201 
202         // Capture requested keys and count down latch if present
203         doAnswer(
204                 new Answer<Boolean>() {
205                     @Override
206                     public Boolean answer(InvocationOnMock input) {
207                         if (mRequestScoresLatch != null) {
208                             mRequestScoresLatch.countDown();
209                         }
210                         NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0];
211                         for (NetworkKey key : keys) {
212                             mRequestedKeys.add(key);
213                         }
214                         return true;
215                     }
216                 }).when(mockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
217 
218         // We use a latch to detect callbacks as Tracker initialization state often invokes
219         // callbacks
220         doAnswer(invocation -> {
221                     if (mAccessPointsChangedLatch != null) {
222                       mAccessPointsChangedLatch.countDown();
223                     }
224                     return null;
225                 }).when(mockWifiListener).onAccessPointsChanged();
226 
227         // Turn on Scoring UI features
228         mOriginalScoringUiSettingValue = Settings.Global.getInt(
229                 InstrumentationRegistry.getTargetContext().getContentResolver(),
230                 Settings.Global.NETWORK_SCORING_UI_ENABLED,
231                 0 /* disabled */);
232         Settings.Global.putInt(
233                 InstrumentationRegistry.getTargetContext().getContentResolver(),
234                 Settings.Global.NETWORK_SCORING_UI_ENABLED,
235                 1 /* enabled */);
236 
237     }
238 
239     @After
cleanUp()240     public void cleanUp() {
241         Settings.Global.putInt(
242                 InstrumentationRegistry.getTargetContext().getContentResolver(),
243                 Settings.Global.NETWORK_SCORING_UI_ENABLED,
244                 mOriginalScoringUiSettingValue);
245     }
246 
buildScanResult1()247     private static ScanResult buildScanResult1() {
248         return new ScanResult(
249                 WifiSsid.fromUtf8Text(SSID_1),
250                 BSSID_1,
251                 0, // hessid
252                 0, //anqpDomainId
253                 null, // osuProviders
254                 "", // capabilities
255                 RSSI_1,
256                 0, // frequency
257                 SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
258     }
259 
buildScanResult2()260     private static ScanResult buildScanResult2() {
261         return new ScanResult(
262                 WifiSsid.fromUtf8Text(SSID_2),
263                 BSSID_2,
264                 0, // hessid
265                 0, //anqpDomainId
266                 null, // osuProviders
267                 "", // capabilities
268                 RSSI_2,
269                 0, // frequency
270                 SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
271     }
272 
buildScanResultWithTimestamp(long timestampMillis)273     private static ScanResult buildScanResultWithTimestamp(long timestampMillis) {
274         return new ScanResult(
275                 WifiSsid.fromUtf8Text(SSID_3),
276                 BSSID_3,
277                 0, // hessid
278                 0, //anqpDomainId
279                 null, // osuProviders
280                 "", // capabilities
281                 RSSI_3,
282                 0, // frequency
283                 timestampMillis * 1000 /* microsecond timestamp */);
284     }
285 
buildPasspointConfiguration(String fqdn, String friendlyName)286     private static WifiConfiguration buildPasspointConfiguration(String fqdn, String friendlyName) {
287         WifiConfiguration config = spy(new WifiConfiguration());
288         config.FQDN = fqdn;
289         config.providerFriendlyName = friendlyName;
290         when(config.isPasspoint()).thenReturn(true);
291         return config;
292     }
293 
294     private List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>>
createPasspointMatchingWifiConfigsWithDuplicates()295             createPasspointMatchingWifiConfigsWithDuplicates() {
296         List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingList =
297                 new ArrayList<>();
298         Map<Integer, List<ScanResult>> mapping = new HashMap<>();
299 
300         mapping.put(WifiManager.PASSPOINT_HOME_NETWORK, Arrays.asList(buildScanResult1()));
301 
302         WifiConfiguration passpointConfig1 =
303                 buildPasspointConfiguration(FQDN_1, PROVIDER_FRIENDLY_NAME_1);
304         WifiConfiguration passpointConfig2 =
305                 buildPasspointConfiguration(FQDN_2, PROVIDER_FRIENDLY_NAME_2);
306 
307         matchingList.add(new Pair(passpointConfig1, mapping));
308         matchingList.add(new Pair(passpointConfig1, mapping));
309         matchingList.add(new Pair(passpointConfig2, mapping));
310         matchingList.add(new Pair(passpointConfig2, mapping));
311 
312         return matchingList;
313     }
314 
315     private List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>>
createPasspointMatchingWifiConfigWithScanResults( List<ScanResult> homeList, List<ScanResult> roamingList, String fqdn, String friendlyName)316             createPasspointMatchingWifiConfigWithScanResults(
317             List<ScanResult> homeList, List<ScanResult> roamingList,
318             String fqdn, String friendlyName) {
319         List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingList =
320                 new ArrayList<>();
321         Map<Integer, List<ScanResult>> mapping = new HashMap<>();
322 
323         if (homeList != null) {
324             mapping.put(WifiManager.PASSPOINT_HOME_NETWORK, homeList);
325         }
326         if (roamingList != null) {
327             mapping.put(WifiManager.PASSPOINT_ROAMING_NETWORK, roamingList);
328         }
329 
330         matchingList.add(new Pair(buildPasspointConfiguration(fqdn, friendlyName),
331                 mapping));
332 
333         return matchingList;
334     }
335 
buildOsuProvider(String friendlyName)336     private static OsuProvider buildOsuProvider(String friendlyName) {
337         Map<String, String> friendlyNames = new HashMap<>();
338         friendlyNames.put("en", friendlyName);
339         return new OsuProvider((WifiSsid) null, friendlyNames, null, null, null, null);
340     }
341 
createTrackerWithImmediateBroadcastsAndInjectInitialScanResults( Intent .... intents)342     private WifiTracker createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(
343                     Intent ... intents)
344             throws InterruptedException {
345         WifiTracker tracker = createMockedWifiTracker();
346 
347         startTracking(tracker);
348         for (Intent intent : intents) {
349             tracker.mReceiver.onReceive(mContext, intent);
350         }
351 
352         sendScanResults(tracker);
353 
354         return tracker;
355     }
356 
createMockedWifiTracker()357     private WifiTracker createMockedWifiTracker() {
358         final WifiTracker wifiTracker = new WifiTracker(
359                 mContext,
360                 mockWifiListener,
361                 mockWifiManager,
362                 mockConnectivityManager,
363                 mockNetworkScoreManager,
364                 new IntentFilter()); // empty filter to ignore system broadcasts
365         wifiTracker.setWorkThread(mWorkerThread);
366         return wifiTracker;
367     }
368 
startTracking(WifiTracker tracker)369     private void startTracking(WifiTracker tracker)  throws InterruptedException {
370         CountDownLatch latch = new CountDownLatch(1);
371         mScannerHandler.post(() -> {
372                 tracker.onStart();
373                 latch.countDown();
374         });
375         assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
376     }
377 
sendScanResults(WifiTracker tracker)378     private void sendScanResults(WifiTracker tracker) throws InterruptedException {
379         Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
380         tracker.mReceiver.onReceive(mContext, i);
381     }
382 
sendFailedScanResults(WifiTracker tracker)383     private void sendFailedScanResults(WifiTracker tracker) throws InterruptedException {
384         Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
385         i.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
386         tracker.mReceiver.onReceive(mContext, i);
387     }
388 
sendUpdatedScores()389     private void sendUpdatedScores() throws InterruptedException {
390         Bundle attr1 = new Bundle();
391         attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve1);
392         ScoredNetwork sc1 =
393                 new ScoredNetwork(
394                         NETWORK_KEY_1,
395                         mockCurve1,
396                         false /* meteredHint */,
397                         attr1);
398 
399         Bundle attr2 = new Bundle();
400         attr2.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve2);
401         ScoredNetwork sc2 =
402                 new ScoredNetwork(
403                         NETWORK_KEY_2,
404                         mockCurve2,
405                         true /* meteredHint */,
406                         attr2);
407 
408         WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
409         scoreCache.updateScores(Arrays.asList(sc1, sc2));
410     }
411 
createTrackerWithScanResultsAndAccessPoint1Connected()412     private WifiTracker createTrackerWithScanResultsAndAccessPoint1Connected()
413             throws InterruptedException {
414         when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO);
415 
416         WifiConfiguration configuration = new WifiConfiguration();
417         configuration.SSID = SSID_1;
418         configuration.BSSID = BSSID_1;
419         configuration.networkId = NETWORK_ID_1;
420 
421         NetworkInfo networkInfo = new NetworkInfo(
422                 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
423         networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
424 
425         Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
426         intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
427         WifiTracker tracker =
428                 createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(intent);
429         assertThat(tracker.isConnected()).isTrue();
430         return tracker;
431     }
432 
waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker)433     private void waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker)
434             throws InterruptedException {
435         CountDownLatch workerLatch = new CountDownLatch(1);
436         tracker.mWorkHandler.post(() -> workerLatch.countDown());
437         assertTrue("Latch timed out while waiting for WorkerHandler",
438                 workerLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
439     }
440 
switchToNetwork2(WifiTracker tracker)441     private void switchToNetwork2(WifiTracker tracker) throws InterruptedException {
442         NetworkInfo networkInfo = new NetworkInfo(
443                 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
444         networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTING, "connecting", "test");
445 
446         WifiInfo info = new WifiInfo();
447         info.setSSID(WifiSsid.fromUtf8Text(SSID_2));
448         info.setBSSID(BSSID_2);
449         info.setRssi(CONNECTED_RSSI);
450         info.setNetworkId(NETWORK_ID_2);
451         when(mockWifiManager.getConnectionInfo()).thenReturn(info);
452 
453         Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
454         intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
455         tracker.mReceiver.onReceive(mContext, intent);
456     }
457 
458     @Test
startAndStopTrackingShouldRegisterAndUnregisterScoreCache()459     public void startAndStopTrackingShouldRegisterAndUnregisterScoreCache()
460             throws InterruptedException {
461         WifiTracker tracker = createMockedWifiTracker();
462 
463         // Test register
464         startTracking(tracker);
465         verify(mockNetworkScoreManager)
466                 .registerNetworkScoreCache(
467                           Matchers.anyInt(),
468                           mScoreCacheCaptor.capture(),
469                           Matchers.anyInt());
470 
471         WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
472 
473         CountDownLatch latch = new CountDownLatch(1);
474         doAnswer(
475                 (invocation) -> {
476                         latch.countDown();
477                         return null;
478                 }).when(mockNetworkScoreManager)
479                         .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
480 
481         // Test unregister
482         tracker.onStop();
483 
484         assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
485         verify(mockNetworkScoreManager)
486                 .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
487     }
488 
489     @Test
testGetNumSavedNetworks()490     public void testGetNumSavedNetworks() throws InterruptedException {
491         WifiConfiguration validConfig = new WifiConfiguration();
492         validConfig.SSID = SSID_1;
493         validConfig.BSSID = BSSID_1;
494 
495         WifiConfiguration selfAddedNoAssociation = new WifiConfiguration();
496         selfAddedNoAssociation.ephemeral = true;
497         selfAddedNoAssociation.numAssociation = 0;
498         selfAddedNoAssociation.SSID = SSID_2;
499         selfAddedNoAssociation.BSSID = BSSID_2;
500 
501         when(mockWifiManager.getConfiguredNetworks())
502                 .thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation));
503 
504         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
505 
506         assertEquals(1, tracker.getNumSavedNetworks());
507     }
508 
509     @Test
startTrackingShouldSetConnectedAccessPointAsActive()510     public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
511         WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
512 
513         List<AccessPoint> aps = tracker.getAccessPoints();
514 
515         assertThat(aps).hasSize(2);
516         assertThat(aps.get(0).isActive()).isTrue();
517     }
518 
519     @Test
startTrackingAfterStopTracking_shouldRequestNewScores()520     public void startTrackingAfterStopTracking_shouldRequestNewScores()
521             throws InterruptedException {
522         // Start the tracker and inject the initial scan results and then stop tracking
523         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
524 
525         tracker.onStop();
526         mRequestedKeys.clear();
527 
528         mRequestScoresLatch = new CountDownLatch(1);
529         startTracking(tracker);
530         assertTrue("Latch timed out",
531                 mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
532 
533         assertTrue(mRequestedKeys.contains(NETWORK_KEY_1));
534         assertTrue(mRequestedKeys.contains(NETWORK_KEY_2));
535     }
536 
537     @Test
stopTracking_shouldNotClearExistingScores()538     public void stopTracking_shouldNotClearExistingScores()
539             throws InterruptedException {
540         // Start the tracker and inject the initial scan results and then stop tracking
541         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
542         updateScoresAndWaitForCacheListenerToProcess(tracker);
543         tracker.onStop();
544 
545         assertThat(mScoreCacheCaptor.getValue().getScoredNetwork(NETWORK_KEY_1)).isNotNull();
546     }
547 
548     @Test
scoreCacheUpdateScoresShouldTriggerOnAccessPointsChanged()549     public void scoreCacheUpdateScoresShouldTriggerOnAccessPointsChanged()
550             throws InterruptedException {
551         WifiTracker tracker = createMockedWifiTracker();
552         startTracking(tracker);
553         sendScanResults(tracker);
554 
555         updateScoresAndWaitForCacheListenerToProcess(tracker);
556     }
557 
updateScoresAndWaitForCacheListenerToProcess(WifiTracker tracker)558     private void updateScoresAndWaitForCacheListenerToProcess(WifiTracker tracker)
559             throws InterruptedException {
560         // Scores are updated via the cache listener hence we need to wait for the work handler
561         // to finish before proceeding.
562         sendUpdatedScores();
563 
564         // Ensure the work handler has processed the scores inside the cache listener of WifiTracker
565         waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
566     }
567 
568     @Test
scoreCacheUpdateScoresShouldChangeSortOrder()569     public void scoreCacheUpdateScoresShouldChangeSortOrder() throws InterruptedException {
570         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
571         List<AccessPoint> aps = tracker.getAccessPoints();
572         assertTrue(aps.size() == 2);
573         assertEquals(aps.get(0).getSsidStr(), SSID_1);
574         assertEquals(aps.get(1).getSsidStr(), SSID_2);
575 
576         updateScoresAndWaitForCacheListenerToProcess(tracker);
577 
578         aps = tracker.getAccessPoints();
579         assertTrue(aps.size() == 2);
580         assertEquals(aps.get(0).getSsidStr(), SSID_2);
581         assertEquals(aps.get(1).getSsidStr(), SSID_1);
582     }
583 
584     @Test
scoreCacheUpdateScoresShouldNotChangeSortOrderWhenSortingDisabled()585     public void scoreCacheUpdateScoresShouldNotChangeSortOrderWhenSortingDisabled()
586             throws InterruptedException {
587         Settings.Global.putInt(
588                 InstrumentationRegistry.getTargetContext().getContentResolver(),
589                 Settings.Global.NETWORK_SCORING_UI_ENABLED,
590                 0 /* disabled */);
591 
592         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
593         List<AccessPoint> aps = tracker.getAccessPoints();
594         assertTrue(aps.size() == 2);
595         assertEquals(aps.get(0).getSsidStr(), SSID_1);
596         assertEquals(aps.get(1).getSsidStr(), SSID_2);
597 
598         updateScoresAndWaitForCacheListenerToProcess(tracker);
599 
600         aps = tracker.getAccessPoints();
601         assertTrue(aps.size() == 2);
602         assertEquals(aps.get(0).getSsidStr(), SSID_1);
603         assertEquals(aps.get(1).getSsidStr(), SSID_2);
604     }
605 
606     @Test
scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()607     public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()
608             throws InterruptedException {
609         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
610         updateScoresAndWaitForCacheListenerToProcess(tracker);
611 
612         List<AccessPoint> aps = tracker.getAccessPoints();
613 
614         for (AccessPoint ap : aps) {
615             if (ap.getSsidStr().equals(SSID_1)) {
616                 assertEquals(BADGE_1, ap.getSpeed());
617             } else if (ap.getSsidStr().equals(SSID_2)) {
618                 assertEquals(BADGE_2, ap.getSpeed());
619             }
620         }
621     }
622 
623     @Test
scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()624     public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()
625             throws InterruptedException {
626         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
627         updateScoresAndWaitForCacheListenerToProcess(tracker);
628 
629         List<AccessPoint> aps = tracker.getAccessPoints();
630 
631         for (AccessPoint ap : aps) {
632             if (ap.getSsidStr().equals(SSID_1)) {
633                 assertFalse(ap.isMetered());
634             } else if (ap.getSsidStr().equals(SSID_2)) {
635                 assertTrue(ap.isMetered());
636             }
637         }
638     }
639 
640     @Test
noSpeedsShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()641     public void noSpeedsShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
642             throws InterruptedException {
643         Settings.Global.putInt(
644                 InstrumentationRegistry.getTargetContext().getContentResolver(),
645                 Settings.Global.NETWORK_SCORING_UI_ENABLED,
646                 0 /* disabled */);
647 
648         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
649         updateScoresAndWaitForCacheListenerToProcess(tracker);
650 
651         List<AccessPoint> aps = tracker.getAccessPoints();
652 
653         for (AccessPoint ap : aps) {
654             if (ap.getSsidStr().equals(SSID_1)) {
655                 assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
656             } else if (ap.getSsidStr().equals(SSID_2)) {
657                 assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
658             }
659         }
660     }
661 
662     @Test
scoresShouldBeRequestedForNewScanResultOnly()663     public void scoresShouldBeRequestedForNewScanResultOnly()  throws InterruptedException {
664         // Scores can be requested together or serially depending on how the scan results are
665         // processed.
666         mRequestScoresLatch = new CountDownLatch(1);
667         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
668         assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
669         mRequestedKeys.clear();
670 
671         String ssid = "ssid3";
672         String bssid = "00:00:00:00:00:00";
673         ScanResult newResult = new ScanResult(
674                 WifiSsid.fromUtf8Text(ssid),
675                 bssid,
676                 0, // hessid
677                 0, //anqpDomainId
678                 null, // osuProviders
679                 "", // capabilities
680                 RSSI_1,
681                 0, // frequency
682                 SystemClock.elapsedRealtime() * 1000);
683         when(mockWifiManager.getScanResults())
684                 .thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2(), newResult));
685 
686         mRequestScoresLatch = new CountDownLatch(1);
687         sendScanResults(tracker);
688         assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
689 
690         assertEquals(1, mRequestedKeys.size());
691         assertTrue(mRequestedKeys.contains(new NetworkKey(new WifiKey('"' + ssid + '"', bssid))));
692     }
693 
694     @Test
scoreCacheAndListenerShouldBeUnregisteredWhenStopTrackingIsCalled()695     public void scoreCacheAndListenerShouldBeUnregisteredWhenStopTrackingIsCalled() throws Exception
696     {
697         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
698         WifiNetworkScoreCache cache = mScoreCacheCaptor.getValue();
699 
700         tracker.onStop();
701         verify(mockNetworkScoreManager).unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, cache);
702 
703         // Verify listener is unregistered so updating a score does not throw an error by posting
704         // a message to the dead work handler
705         mWorkerThread.quit();
706         sendUpdatedScores();
707     }
708 
709     /**
710      * Verify that tracking a Passpoint AP on a device with Passpoint disabled doesn't cause
711      * any crash.
712      *
713      * @throws Exception
714      */
715     @Test
trackPasspointApWithPasspointDisabled()716     public void trackPasspointApWithPasspointDisabled() throws Exception {
717         // TODO(sghuman): Delete this test and replace with a passpoint test
718         WifiTracker tracker = createMockedWifiTracker();
719 
720         // Add a Passpoint AP to the scan results.
721         List<ScanResult> results = new ArrayList<>();
722         ScanResult passpointAp = new ScanResult(
723                 WifiSsid.fromUtf8Text(SSID_1),
724                 BSSID_1,
725                 0, // hessid
726                 0, //anqpDomainId
727                 null, // osuProviders
728                 "", // capabilities
729                 RSSI_1,
730                 0, // frequency
731                 SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
732         passpointAp.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
733         results.add(passpointAp);
734 
735         // Update access point and verify UnsupportedOperationException is being caught for
736         // call to WifiManager#getMatchingWifiConfig.
737         when(mockWifiManager.getConfiguredNetworks())
738                 .thenReturn(new ArrayList<WifiConfiguration>());
739         when(mockWifiManager.getScanResults()).thenReturn(results);
740 
741         startTracking(tracker);
742     }
743 
744     @Test
rssiChangeBroadcastShouldUpdateConnectedAp()745     public void rssiChangeBroadcastShouldUpdateConnectedAp() throws Exception {
746         WifiTracker tracker =  createTrackerWithScanResultsAndAccessPoint1Connected();
747         assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
748 
749         int newRssi = CONNECTED_RSSI + 10;
750         WifiInfo info = new WifiInfo(CONNECTED_AP_1_INFO);
751         info.setRssi(newRssi);
752 
753         // Once the new info has been fetched, we need to wait for the access points to be copied
754         mAccessPointsChangedLatch = new CountDownLatch(1);
755         doAnswer(invocation -> info).when(mockWifiManager).getConnectionInfo();
756 
757         tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.RSSI_CHANGED_ACTION));
758 
759         assertTrue("onAccessPointsChanged never called",
760                 mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
761         assertThat(tracker.getAccessPoints().get(0).getRssi()).isEqualTo(newRssi);
762     }
763 
764     @Test
onStartShouldSynchronouslyFetchLatestInformation()765     public void onStartShouldSynchronouslyFetchLatestInformation() throws Exception {
766         Network mockNetwork = mock(Network.class);
767         when(mockWifiManager.getCurrentNetwork()).thenReturn(mockNetwork);
768 
769         when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO);
770 
771         NetworkInfo networkInfo = new NetworkInfo(
772                 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
773         networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
774         when(mockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(networkInfo);
775 
776         WifiTracker tracker = createMockedWifiTracker();
777         startTracking(tracker);
778 
779         verify(mockWifiManager).getConnectionInfo();
780         verify(mockWifiManager, times(1)).getConfiguredNetworks();
781         verify(mockConnectivityManager).getNetworkInfo(any(Network.class));
782 
783         // mStaleAccessPoints is true
784         verify(mockWifiListener, never()).onAccessPointsChanged();
785         assertThat(tracker.getAccessPoints().size()).isEqualTo(2);
786         assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
787     }
788 
789     @Test
onStartShouldDisplayConnectedAccessPointWhenThereAreNoScanResults()790     public void onStartShouldDisplayConnectedAccessPointWhenThereAreNoScanResults()
791             throws Exception {
792         Network mockNetwork = mock(Network.class);
793         when(mockWifiManager.getCurrentNetwork()).thenReturn(mockNetwork);
794 
795         when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO);
796 
797         NetworkInfo networkInfo = new NetworkInfo(
798                 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
799         networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
800         when(mockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(networkInfo);
801 
802         // Don't return any scan results
803         when(mockWifiManager.getScanResults()).thenReturn(new ArrayList<>());
804 
805         WifiTracker tracker = createMockedWifiTracker();
806         startTracking(tracker);
807 
808         verify(mockWifiManager).getConnectionInfo();
809         verify(mockWifiManager, times(1)).getConfiguredNetworks();
810         verify(mockConnectivityManager).getNetworkInfo(any(Network.class));
811 
812         // mStaleAccessPoints is true
813         verify(mockWifiListener, never()).onAccessPointsChanged();
814 
815         assertThat(tracker.getAccessPoints()).hasSize(1);
816         assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
817     }
818 
819     @Test
stopTrackingShouldRemoveAllPendingWork()820     public void stopTrackingShouldRemoveAllPendingWork() throws Exception {
821         WifiTracker tracker = createMockedWifiTracker();
822         startTracking(tracker);
823 
824         CountDownLatch ready = new CountDownLatch(1);
825         CountDownLatch latch = new CountDownLatch(1);
826         CountDownLatch lock = new CountDownLatch(1);
827         tracker.mWorkHandler.post(() -> {
828             try {
829                 ready.countDown();
830                 lock.await();
831                 latch.countDown();
832             } catch (InterruptedException e) {
833                 fail("Interrupted Exception while awaiting lock release: " + e);
834             }
835         });
836 
837         // Enqueue messages
838         final AtomicBoolean executed = new AtomicBoolean(false);
839         tracker.mWorkHandler.post(() -> executed.set(true));
840 
841         try {
842             ready.await(); // Make sure we have entered the first message handler
843         } catch (InterruptedException e) {}
844         tracker.onStop();
845 
846         lock.countDown();
847         assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
848 
849         // In case the method was already executing
850         assertThat(tracker.mWorkHandler.hasMessagesOrCallbacks()).isFalse();
851 
852         assertThat(executed.get()).isFalse();
853     }
854 
855     @Test
stopTrackingShouldPreventCallbacksFromOngoingWork()856     public void stopTrackingShouldPreventCallbacksFromOngoingWork() throws Exception {
857         WifiTracker tracker = createMockedWifiTracker();
858         startTracking(tracker);
859 
860         final CountDownLatch ready = new CountDownLatch(1);
861         final CountDownLatch latch = new CountDownLatch(1);
862         final CountDownLatch lock = new CountDownLatch(1);
863         tracker.mWorkHandler.post(() -> {
864             try {
865                 ready.countDown();
866                 lock.await();
867 
868                 tracker.mReceiver.onReceive(
869                         mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
870 
871                 latch.countDown();
872             } catch (InterruptedException e) {
873                 fail("Interrupted Exception while awaiting lock release: " + e);
874             }
875         });
876 
877         ready.await(); // Make sure we have entered the first message handler
878         tracker.onStop();
879         lock.countDown();
880         assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
881 
882         // Wait for main thread
883         final CountDownLatch latch2 = new CountDownLatch(1);
884         ThreadUtils.postOnMainThread(latch2::countDown);
885         latch2.await();
886 
887         verify(mockWifiListener, never()).onWifiStateChanged(anyInt());
888     }
889 
890     @Test
stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult()891     public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult()
892             throws Exception {
893         WifiTracker tracker = createMockedWifiTracker();
894         startTracking(tracker);
895 
896         tracker.onStop();
897 
898         startTracking(tracker);
899 
900         tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
901         tracker.mReceiver.onReceive(
902                 mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
903         tracker.mReceiver.onReceive(
904                 mContext, new Intent(WifiManager.ACTION_LINK_CONFIGURATION_CHANGED));
905 
906 
907         verify(mockWifiListener, never()).onAccessPointsChanged();
908 
909         sendScanResults(tracker); // verifies onAccessPointsChanged is invoked
910     }
911 
912     @Test
startTrackingShouldNotSendAnyCallbacksUntilScanResultsAreProcessed()913     public void startTrackingShouldNotSendAnyCallbacksUntilScanResultsAreProcessed()
914             throws Exception {
915         WifiTracker tracker = createMockedWifiTracker();
916         startTracking(tracker);
917 
918         tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
919         tracker.mReceiver.onReceive(
920                 mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
921         tracker.mReceiver.onReceive(
922                 mContext, new Intent(WifiManager.ACTION_LINK_CONFIGURATION_CHANGED));
923 
924         verify(mockWifiListener, never()).onAccessPointsChanged();
925 
926         sendScanResults(tracker); // verifies onAccessPointsChanged is invoked
927     }
928 
929     @Test
disablingWifiShouldClearExistingAccessPoints()930     public void disablingWifiShouldClearExistingAccessPoints() throws Exception {
931         WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
932 
933         when(mockWifiManager.isWifiEnabled()).thenReturn(false);
934 
935         mAccessPointsChangedLatch = new CountDownLatch(1);
936         tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
937         assertThat(mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
938 
939         assertThat(tracker.getAccessPoints()).isEmpty();
940     }
941 
942     @Test
onConnectedChangedCallback_shouldNotBeInvokedWhenNoStateChange()943     public void onConnectedChangedCallback_shouldNotBeInvokedWhenNoStateChange() throws Exception {
944         WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
945         verify(mockWifiListener, times(1)).onConnectedChanged();
946 
947         NetworkInfo networkInfo = new NetworkInfo(
948                 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
949         networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
950 
951         Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
952         intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
953         tracker.mReceiver.onReceive(mContext, intent);
954 
955         verify(mockWifiListener, times(1)).onConnectedChanged();
956     }
957 
958     @Test
onConnectedChangedCallback_shouldBeInvokedWhenStateChanges()959     public void onConnectedChangedCallback_shouldBeInvokedWhenStateChanges() throws Exception {
960         WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
961         verify(mockWifiListener, times(1)).onConnectedChanged();
962 
963         NetworkInfo networkInfo = new NetworkInfo(
964                 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
965         networkInfo.setDetailedState(
966                 NetworkInfo.DetailedState.DISCONNECTED, "disconnected", "test");
967 
968         Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
969         intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
970         tracker.mReceiver.onReceive(mContext, intent);
971 
972         assertThat(tracker.isConnected()).isFalse();
973         verify(mockWifiListener, times(2)).onConnectedChanged();
974     }
975 
976     @Test
updateNetworkInfoWithNewConnectedNetwork_switchesNetworks()977     public void updateNetworkInfoWithNewConnectedNetwork_switchesNetworks() throws Exception {
978         WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
979 
980         switchToNetwork2(tracker);
981 
982         List<AccessPoint> aps = tracker.getAccessPoints();
983         assertThat(aps.get(0).getSsidStr()).isEqualTo(SSID_2);
984 
985         assertThat(aps.get(0).isReachable()).isTrue();
986         assertThat(aps.get(1).isReachable()).isTrue();
987     }
988 
989     @Test
onStart_updateScanResults_evictOldScanResult()990     public void onStart_updateScanResults_evictOldScanResult() {
991         when(mockWifiManager.getScanResults()).thenReturn(Arrays.asList(
992                 buildScanResult1(), buildScanResult2(), buildScanResultWithTimestamp(0)));
993         WifiTracker tracker = createMockedWifiTracker();
994 
995         tracker.forceUpdate();
996 
997         // Only has scanResult1 and scanResult2
998         assertThat(tracker.getAccessPoints()).hasSize(2);
999         assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1);
1000         assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2);
1001     }
1002 
1003     /**
1004      * Verifies that a failed scan reported on SCAN_RESULTS_AVAILABLE_ACTION should increase the
1005      * ScanResult eviction timeout to twice the default.
1006      */
1007     @Test
failedScan_increasesEvictionTimeout()1008     public void failedScan_increasesEvictionTimeout() throws InterruptedException {
1009         when(mockWifiManager.getScanResults()).thenReturn(Arrays.asList(
1010                 buildScanResult1(), buildScanResult2(), buildScanResultWithTimestamp(
1011                         SystemClock.elapsedRealtime() - WifiTracker.MAX_SCAN_RESULT_AGE_MILLIS)));
1012         WifiTracker tracker = createMockedWifiTracker();
1013 
1014         sendFailedScanResults(tracker);
1015 
1016         // Failed scan increases timeout window to include the stale scan
1017         assertThat(tracker.getAccessPoints()).hasSize(3);
1018         assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1);
1019         assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2);
1020         assertThat(tracker.getAccessPoints().get(2).getBssid()).isEqualTo(BSSID_3);
1021 
1022         sendScanResults(tracker);
1023 
1024         // Successful scan resets the timeout window to remove the stale scan
1025         assertThat(tracker.getAccessPoints()).hasSize(2);
1026         assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1);
1027         assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2);
1028     }
1029 
1030     /**
1031      * Verifies that updatePasspointAccessPoints will only return AccessPoints whose
1032      * isPasspoint() evaluates as true.
1033      */
1034     @Test
updatePasspointAccessPoints_returnedAccessPointsArePasspoint()1035     public void updatePasspointAccessPoints_returnedAccessPointsArePasspoint() {
1036         WifiTracker tracker = createMockedWifiTracker();
1037 
1038         List<AccessPoint> passpointAccessPoints = tracker.updatePasspointAccessPoints(
1039                 createPasspointMatchingWifiConfigsWithDuplicates(), new ArrayList<>());
1040 
1041         assertTrue(passpointAccessPoints.size() != 0);
1042         for (AccessPoint ap : passpointAccessPoints) {
1043             assertTrue(ap.isPasspoint());
1044         }
1045     }
1046 
1047     /**
1048      * Verifies that updatePasspointAccessPoints will return the same amount of AccessPoints as
1049      * unique WifiConfigurations, even if duplicate FQDNs exist.
1050      */
1051     @Test
updatePasspointAccessPoints_ignoresDuplicateFQDNs()1052     public void updatePasspointAccessPoints_ignoresDuplicateFQDNs() {
1053         WifiTracker tracker = createMockedWifiTracker();
1054 
1055         // Process matching list of four configs with two duplicate FQDNs.
1056         List<AccessPoint> passpointAccessPoints = tracker.updatePasspointAccessPoints(
1057                 createPasspointMatchingWifiConfigsWithDuplicates(), new ArrayList<>());
1058 
1059         // Should have 2 APs with unique FQDNs, ignoring the 2 duplicate FQDNs.
1060         assertThat(passpointAccessPoints).hasSize(2);
1061 
1062         Set<String> fqdns = new ArraySet<>(Arrays.asList(FQDN_1, FQDN_2));
1063 
1064         assertTrue(fqdns.remove(passpointAccessPoints.get(0).getConfig().FQDN));
1065         assertTrue(fqdns.remove(passpointAccessPoints.get(1).getConfig().FQDN));
1066     }
1067 
1068     /**
1069      * Verifies that updatePasspointAccessPoints will return matching cached APs and update their
1070      * scan results instead of creating new APs.
1071      */
1072     @Test
updatePasspointAccessPoints_usesCachedAccessPoints()1073     public void updatePasspointAccessPoints_usesCachedAccessPoints() {
1074         WifiTracker tracker = createMockedWifiTracker();
1075 
1076         ScanResult result = buildScanResult1();
1077 
1078         List<AccessPoint> passpointAccessPointsFirstUpdate = tracker.updatePasspointAccessPoints(
1079                 createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result),
1080                         null, FQDN_1, PROVIDER_FRIENDLY_NAME_1), new ArrayList<>());
1081         List<AccessPoint> cachedAccessPoints = new ArrayList<>(passpointAccessPointsFirstUpdate);
1082 
1083         int prevRssi = result.level;
1084         int newRssi = prevRssi + 10;
1085         result.level = newRssi;
1086 
1087         List<AccessPoint> passpointAccessPointsSecondUpdate = tracker.updatePasspointAccessPoints(
1088                 createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result),
1089                         null, FQDN_1, PROVIDER_FRIENDLY_NAME_1), cachedAccessPoints);
1090 
1091         // Verify second update AP is the same object as the first update AP
1092         assertThat(passpointAccessPointsFirstUpdate.get(0))
1093                 .isSameInstanceAs(passpointAccessPointsSecondUpdate.get(0));
1094         // Verify second update AP has the average of the first and second update RSSIs
1095         assertThat(passpointAccessPointsSecondUpdate.get(0).getRssi())
1096                 .isEqualTo((prevRssi + newRssi) / 2);
1097     }
1098 
1099     /**
1100      * Verifies that the internal WifiConfiguration of a Passpoint AccessPoint is updated
1101      */
1102     @Test
updatePasspointAccessPoints_updatesConfig()1103     public void updatePasspointAccessPoints_updatesConfig() {
1104         WifiTracker tracker = createMockedWifiTracker();
1105 
1106         ScanResult result = buildScanResult1();
1107 
1108         List<AccessPoint> passpointAccessPoints = tracker.updatePasspointAccessPoints(
1109                 createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result),
1110                         null, FQDN_1, PROVIDER_FRIENDLY_NAME_1), new ArrayList<>());
1111 
1112         AccessPoint ap = passpointAccessPoints.get(0);
1113         assertEquals(ap.getTitle(), PROVIDER_FRIENDLY_NAME_1);
1114 
1115         tracker.updatePasspointAccessPoints(
1116                 createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result),
1117                         null, FQDN_1, PROVIDER_FRIENDLY_NAME_2), passpointAccessPoints);
1118         assertEquals(ap.getTitle(), PROVIDER_FRIENDLY_NAME_2);
1119     }
1120 
1121     /**
1122      * Verifies that updateOsuAccessPoints will only return AccessPoints whose
1123      * isOsuProvider() evaluates as true.
1124      */
1125     @Test
updateOsuAccessPoints_returnedAccessPointsAreOsuProviders()1126     public void updateOsuAccessPoints_returnedAccessPointsAreOsuProviders() {
1127         WifiTracker tracker = createMockedWifiTracker();
1128 
1129         Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>();
1130         providersAndScans.put(
1131                 buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(buildScanResult1()));
1132         providersAndScans.put(
1133                 buildOsuProvider(PROVIDER_FRIENDLY_NAME_2), Arrays.asList(buildScanResult2()));
1134 
1135         List<AccessPoint> osuAccessPoints = tracker.updateOsuAccessPoints(
1136                 providersAndScans, new ArrayList<>());
1137 
1138         assertThat(osuAccessPoints).hasSize(2);
1139         for (AccessPoint ap: osuAccessPoints) {
1140             assertThat(ap.isOsuProvider()).isTrue();
1141         }
1142     }
1143 
1144     /**
1145      * Verifies that updateOsuAccessPoints will not return Osu AccessPoints for already provisioned
1146      * networks
1147      */
1148     @Test
updateOsuAccessPoints_doesNotReturnAlreadyProvisionedOsuAccessPoints()1149     public void updateOsuAccessPoints_doesNotReturnAlreadyProvisionedOsuAccessPoints() {
1150         WifiTracker tracker = createMockedWifiTracker();
1151 
1152         // Start with two Osu Providers
1153         Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>();
1154         providersAndScans.put(
1155                 buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(buildScanResult1()));
1156         providersAndScans.put(
1157                 buildOsuProvider(PROVIDER_FRIENDLY_NAME_2), Arrays.asList(buildScanResult2()));
1158 
1159         // First update
1160         List<AccessPoint> osuAccessPoints = tracker.updateOsuAccessPoints(
1161                 providersAndScans, new ArrayList<>());
1162 
1163         // Make sure both Osu Providers' APs are returned
1164         assertThat(osuAccessPoints).hasSize(2);
1165         List<String> friendlyNames = Arrays.asList(
1166                 osuAccessPoints.get(0).getTitle(), osuAccessPoints.get(1).getTitle());
1167         assertThat(friendlyNames)
1168                 .containsExactly(PROVIDER_FRIENDLY_NAME_1, PROVIDER_FRIENDLY_NAME_2);
1169 
1170         // Simulate Osu Provider 1 being provisioned
1171         Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigForOsuProvider =
1172                 new HashMap<>();
1173         matchingPasspointConfigForOsuProvider.put(buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), null);
1174         when(mockWifiManager.getMatchingPasspointConfigsForOsuProviders(any())).thenReturn(
1175                 matchingPasspointConfigForOsuProvider);
1176 
1177         // Second update
1178         osuAccessPoints = tracker.updateOsuAccessPoints(
1179                 providersAndScans, new ArrayList<>());
1180 
1181         // Returned AP should only be for Osu Provider 2
1182         assertThat(osuAccessPoints).hasSize(1);
1183         assertThat(osuAccessPoints.get(0).getTitle()).isEqualTo(PROVIDER_FRIENDLY_NAME_2);
1184     }
1185 
1186     /**
1187      * Verifies that updateOsuAccessPoints will return matching cached APs and update their
1188      * scan results instead of creating new APs.
1189      */
1190     @Test
updateOsuAccessPoints_usesCachedAccessPoints()1191     public void updateOsuAccessPoints_usesCachedAccessPoints() {
1192         WifiTracker tracker = createMockedWifiTracker();
1193 
1194         ScanResult result = buildScanResult1();
1195 
1196         Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>();
1197         providersAndScans.put(
1198                 buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(result));
1199 
1200         List<AccessPoint> osuAccessPointsFirstUpdate = tracker.updateOsuAccessPoints(
1201                 providersAndScans, new ArrayList<>());
1202         List<AccessPoint> cachedAccessPoints = new ArrayList<>(osuAccessPointsFirstUpdate);
1203 
1204         // New RSSI for second update
1205         int prevRssi = result.level;
1206         int newRssi = prevRssi + 10;
1207         result.level = newRssi;
1208 
1209         List<AccessPoint> osuAccessPointsSecondUpdate = tracker.updateOsuAccessPoints(
1210                 providersAndScans, cachedAccessPoints);
1211 
1212         // Verify second update AP is the same object as the first update AP
1213         assertThat(osuAccessPointsFirstUpdate.get(0))
1214                 .isSameInstanceAs(osuAccessPointsSecondUpdate.get(0));
1215         // Verify second update AP has the average of the first and second update RSSIs
1216         assertThat(osuAccessPointsSecondUpdate.get(0).getRssi())
1217                 .isEqualTo((prevRssi + newRssi) / 2);
1218     }
1219 }
1220