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.ArgumentMatchers.eq; 25 import static org.mockito.Mockito.any; 26 import static org.mockito.Mockito.anyInt; 27 import static org.mockito.Mockito.doAnswer; 28 import static org.mockito.Mockito.doNothing; 29 import static org.mockito.Mockito.mock; 30 import static org.mockito.Mockito.never; 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.os.Bundle; 53 import android.os.Handler; 54 import android.os.HandlerThread; 55 import android.os.SystemClock; 56 import android.provider.Settings; 57 import android.support.test.InstrumentationRegistry; 58 import android.support.test.filters.SmallTest; 59 import android.support.test.runner.AndroidJUnit4; 60 61 import com.android.settingslib.utils.ThreadUtils; 62 63 import org.junit.After; 64 import org.junit.Before; 65 import org.junit.Test; 66 import org.junit.runner.RunWith; 67 import org.mockito.ArgumentCaptor; 68 import org.mockito.Captor; 69 import org.mockito.Matchers; 70 import org.mockito.Mock; 71 import org.mockito.MockitoAnnotations; 72 import org.mockito.invocation.InvocationOnMock; 73 import org.mockito.stubbing.Answer; 74 75 import java.util.ArrayList; 76 import java.util.Arrays; 77 import java.util.List; 78 import java.util.concurrent.CountDownLatch; 79 import java.util.concurrent.TimeUnit; 80 import java.util.concurrent.atomic.AtomicBoolean; 81 82 // TODO(sghuman): Change these to robolectric tests b/35766684. 83 84 @SmallTest 85 @RunWith(AndroidJUnit4.class) 86 public class WifiTrackerTest { 87 88 private static final String TAG = "WifiTrackerTest"; 89 private static final int LATCH_TIMEOUT = 4000; 90 91 private static final String SSID_1 = "ssid1"; 92 private static final String BSSID_1 = "00:00:00:00:00:00"; 93 private static final NetworkKey NETWORK_KEY_1 = 94 new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1)); 95 private static final int RSSI_1 = -30; 96 private static final byte SCORE_1 = 10; 97 private static final int BADGE_1 = AccessPoint.Speed.MODERATE; 98 99 private static final String SSID_2 = "ssid2"; 100 private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA"; 101 private static final NetworkKey NETWORK_KEY_2 = 102 new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2)); 103 private static final int RSSI_2 = -30; 104 private static final byte SCORE_2 = 15; 105 private static final int BADGE_2 = AccessPoint.Speed.FAST; 106 107 // TODO(b/65594609): Convert mutable Data objects to instance variables / builder pattern 108 private static final int NETWORK_ID_1 = 123; 109 private static final int CONNECTED_RSSI = -50; 110 private static final WifiInfo CONNECTED_AP_1_INFO = new WifiInfo(); 111 static { 112 CONNECTED_AP_1_INFO.setSSID(WifiSsid.createFromAsciiEncoded(SSID_1)); 113 CONNECTED_AP_1_INFO.setBSSID(BSSID_1); 114 CONNECTED_AP_1_INFO.setNetworkId(NETWORK_ID_1); 115 CONNECTED_AP_1_INFO.setRssi(CONNECTED_RSSI); 116 } 117 private static final WifiConfiguration CONFIGURATION_1 = new WifiConfiguration(); 118 static { 119 CONFIGURATION_1.SSID = SSID_1; 120 CONFIGURATION_1.BSSID = BSSID_1; 121 CONFIGURATION_1.networkId = NETWORK_ID_1; 122 } 123 124 private static final int NETWORK_ID_2 = 2; 125 private static final WifiConfiguration CONFIGURATION_2 = new WifiConfiguration(); 126 static { 127 CONFIGURATION_2.SSID = SSID_2; 128 CONFIGURATION_2.BSSID = BSSID_2; 129 CONFIGURATION_2.networkId = NETWORK_ID_2; 130 } 131 132 @Captor ArgumentCaptor<WifiNetworkScoreCache> mScoreCacheCaptor; 133 @Mock private ConnectivityManager mockConnectivityManager; 134 @Mock private NetworkScoreManager mockNetworkScoreManager; 135 @Mock private RssiCurve mockCurve1; 136 @Mock private RssiCurve mockCurve2; 137 @Mock private RssiCurve mockBadgeCurve1; 138 @Mock private RssiCurve mockBadgeCurve2; 139 @Mock private WifiManager mockWifiManager; 140 @Mock private WifiTracker.WifiListener mockWifiListener; 141 142 private final List<NetworkKey> mRequestedKeys = new ArrayList<>(); 143 144 private Context mContext; 145 private CountDownLatch mAccessPointsChangedLatch; 146 private CountDownLatch mRequestScoresLatch; 147 private Handler mScannerHandler; 148 private HandlerThread mWorkerThread; 149 150 private int mOriginalScoringUiSettingValue; 151 152 @SuppressWarnings("VisibleForTests") 153 @Before 154 public void setUp() { 155 MockitoAnnotations.initMocks(this); 156 157 mContext = InstrumentationRegistry.getTargetContext(); 158 159 mWorkerThread = new HandlerThread("TestHandlerWorkerThread"); 160 mWorkerThread.start(); 161 162 // Make sure the scanner doesn't try to run on the testing thread. 163 HandlerThread scannerThread = new HandlerThread("ScannerWorkerThread"); 164 scannerThread.start(); 165 mScannerHandler = new Handler(scannerThread.getLooper()); 166 167 when(mockWifiManager.isWifiEnabled()).thenReturn(true); 168 when(mockWifiManager.getScanResults()) 169 .thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2())); 170 when(mockWifiManager.getConfiguredNetworks()) 171 .thenReturn(Arrays.asList(CONFIGURATION_1, CONFIGURATION_2)); 172 173 174 when(mockCurve1.lookupScore(RSSI_1)).thenReturn(SCORE_1); 175 when(mockCurve2.lookupScore(RSSI_2)).thenReturn(SCORE_2); 176 177 when(mockBadgeCurve1.lookupScore(RSSI_1)).thenReturn((byte) BADGE_1); 178 when(mockBadgeCurve2.lookupScore(RSSI_2)).thenReturn((byte) BADGE_2); 179 180 doNothing() 181 .when(mockNetworkScoreManager) 182 .registerNetworkScoreCache( 183 anyInt(), 184 mScoreCacheCaptor.capture(), 185 Matchers.anyInt()); 186 187 // Capture requested keys and count down latch if present 188 doAnswer( 189 new Answer<Boolean>() { 190 @Override 191 public Boolean answer(InvocationOnMock input) { 192 if (mRequestScoresLatch != null) { 193 mRequestScoresLatch.countDown(); 194 } 195 NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0]; 196 for (NetworkKey key : keys) { 197 mRequestedKeys.add(key); 198 } 199 return true; 200 } 201 }).when(mockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any()); 202 203 // We use a latch to detect callbacks as Tracker initialization state often invokes 204 // callbacks 205 doAnswer(invocation -> { 206 if (mAccessPointsChangedLatch != null) { 207 mAccessPointsChangedLatch.countDown(); 208 } 209 return null; 210 }).when(mockWifiListener).onAccessPointsChanged(); 211 212 // Turn on Scoring UI features 213 mOriginalScoringUiSettingValue = Settings.Global.getInt( 214 InstrumentationRegistry.getTargetContext().getContentResolver(), 215 Settings.Global.NETWORK_SCORING_UI_ENABLED, 216 0 /* disabled */); 217 Settings.Global.putInt( 218 InstrumentationRegistry.getTargetContext().getContentResolver(), 219 Settings.Global.NETWORK_SCORING_UI_ENABLED, 220 1 /* enabled */); 221 222 } 223 224 @After 225 public void cleanUp() { 226 Settings.Global.putInt( 227 InstrumentationRegistry.getTargetContext().getContentResolver(), 228 Settings.Global.NETWORK_SCORING_UI_ENABLED, 229 mOriginalScoringUiSettingValue); 230 } 231 232 private static ScanResult buildScanResult1() { 233 return new ScanResult( 234 WifiSsid.createFromAsciiEncoded(SSID_1), 235 BSSID_1, 236 0, // hessid 237 0, //anqpDomainId 238 null, // osuProviders 239 "", // capabilities 240 RSSI_1, 241 0, // frequency 242 SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */); 243 } 244 245 private static ScanResult buildScanResult2() { 246 return new ScanResult( 247 WifiSsid.createFromAsciiEncoded(SSID_2), 248 BSSID_2, 249 0, // hessid 250 0, //anqpDomainId 251 null, // osuProviders 252 "", // capabilities 253 RSSI_2, 254 0, // frequency 255 SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */); 256 } 257 258 private WifiTracker createTrackerWithImmediateBroadcastsAndInjectInitialScanResults( 259 Intent ... intents) 260 throws InterruptedException { 261 WifiTracker tracker = createMockedWifiTracker(); 262 263 startTracking(tracker); 264 for (Intent intent : intents) { 265 tracker.mReceiver.onReceive(mContext, intent); 266 } 267 268 sendScanResults(tracker); 269 270 return tracker; 271 } 272 273 private WifiTracker createMockedWifiTracker() { 274 final WifiTracker wifiTracker = new WifiTracker( 275 mContext, 276 mockWifiListener, 277 mockWifiManager, 278 mockConnectivityManager, 279 mockNetworkScoreManager, 280 new IntentFilter()); // empty filter to ignore system broadcasts 281 wifiTracker.setWorkThread(mWorkerThread); 282 return wifiTracker; 283 } 284 285 private void startTracking(WifiTracker tracker) throws InterruptedException { 286 CountDownLatch latch = new CountDownLatch(1); 287 mScannerHandler.post(() -> { 288 tracker.onStart(); 289 latch.countDown(); 290 }); 291 assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 292 } 293 294 private void sendScanResults(WifiTracker tracker) throws InterruptedException { 295 Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 296 tracker.mReceiver.onReceive(mContext, i); 297 } 298 299 private void sendUpdatedScores() throws InterruptedException { 300 Bundle attr1 = new Bundle(); 301 attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve1); 302 ScoredNetwork sc1 = 303 new ScoredNetwork( 304 NETWORK_KEY_1, 305 mockCurve1, 306 false /* meteredHint */, 307 attr1); 308 309 Bundle attr2 = new Bundle(); 310 attr2.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve2); 311 ScoredNetwork sc2 = 312 new ScoredNetwork( 313 NETWORK_KEY_2, 314 mockCurve2, 315 true /* meteredHint */, 316 attr2); 317 318 WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue(); 319 scoreCache.updateScores(Arrays.asList(sc1, sc2)); 320 } 321 322 private WifiTracker createTrackerWithScanResultsAndAccessPoint1Connected() 323 throws InterruptedException { 324 when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO); 325 326 WifiConfiguration configuration = new WifiConfiguration(); 327 configuration.SSID = SSID_1; 328 configuration.BSSID = BSSID_1; 329 configuration.networkId = NETWORK_ID_1; 330 331 NetworkInfo networkInfo = new NetworkInfo( 332 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype"); 333 networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test"); 334 335 Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); 336 intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); 337 WifiTracker tracker = 338 createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(intent); 339 assertThat(tracker.isConnected()).isTrue(); 340 return tracker; 341 } 342 343 private void waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker) 344 throws InterruptedException { 345 CountDownLatch workerLatch = new CountDownLatch(1); 346 tracker.mWorkHandler.post(() -> workerLatch.countDown()); 347 assertTrue("Latch timed out while waiting for WorkerHandler", 348 workerLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 349 } 350 351 private void switchToNetwork2(WifiTracker tracker) throws InterruptedException { 352 NetworkInfo networkInfo = new NetworkInfo( 353 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype"); 354 networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTING, "connecting", "test"); 355 356 WifiInfo info = new WifiInfo(); 357 info.setSSID(WifiSsid.createFromAsciiEncoded(SSID_2)); 358 info.setBSSID(BSSID_2); 359 info.setRssi(CONNECTED_RSSI); 360 info.setNetworkId(NETWORK_ID_2); 361 when(mockWifiManager.getConnectionInfo()).thenReturn(info); 362 363 Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); 364 intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); 365 tracker.mReceiver.onReceive(mContext, intent); 366 } 367 368 @Test 369 public void startAndStopTrackingShouldRegisterAndUnregisterScoreCache() 370 throws InterruptedException { 371 WifiTracker tracker = createMockedWifiTracker(); 372 373 // Test register 374 startTracking(tracker); 375 verify(mockNetworkScoreManager) 376 .registerNetworkScoreCache( 377 Matchers.anyInt(), 378 mScoreCacheCaptor.capture(), 379 Matchers.anyInt()); 380 381 WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue(); 382 383 CountDownLatch latch = new CountDownLatch(1); 384 doAnswer( 385 (invocation) -> { 386 latch.countDown(); 387 return null; 388 }).when(mockNetworkScoreManager) 389 .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache); 390 391 // Test unregister 392 tracker.onStop(); 393 394 assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 395 verify(mockNetworkScoreManager) 396 .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache); 397 } 398 399 @Test 400 public void testGetNumSavedNetworks() throws InterruptedException { 401 WifiConfiguration validConfig = new WifiConfiguration(); 402 validConfig.SSID = SSID_1; 403 validConfig.BSSID = BSSID_1; 404 405 WifiConfiguration selfAddedNoAssociation = new WifiConfiguration(); 406 selfAddedNoAssociation.ephemeral = true; 407 selfAddedNoAssociation.selfAdded = true; 408 selfAddedNoAssociation.numAssociation = 0; 409 selfAddedNoAssociation.SSID = SSID_2; 410 selfAddedNoAssociation.BSSID = BSSID_2; 411 412 when(mockWifiManager.getConfiguredNetworks()) 413 .thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation)); 414 415 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 416 417 assertEquals(1, tracker.getNumSavedNetworks()); 418 } 419 420 @Test 421 public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException { 422 WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected(); 423 424 List<AccessPoint> aps = tracker.getAccessPoints(); 425 426 assertThat(aps).hasSize(2); 427 assertThat(aps.get(0).isActive()).isTrue(); 428 } 429 430 @Test 431 public void startTrackingAfterStopTracking_shouldRequestNewScores() 432 throws InterruptedException { 433 // Start the tracker and inject the initial scan results and then stop tracking 434 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 435 436 tracker.onStop(); 437 mRequestedKeys.clear(); 438 439 mRequestScoresLatch = new CountDownLatch(1); 440 startTracking(tracker); 441 assertTrue("Latch timed out", 442 mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 443 444 assertTrue(mRequestedKeys.contains(NETWORK_KEY_1)); 445 assertTrue(mRequestedKeys.contains(NETWORK_KEY_2)); 446 } 447 448 @Test 449 public void stopTracking_shouldNotClearExistingScores() 450 throws InterruptedException { 451 // Start the tracker and inject the initial scan results and then stop tracking 452 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 453 updateScoresAndWaitForCacheListenerToProcess(tracker); 454 tracker.onStop(); 455 456 assertThat(mScoreCacheCaptor.getValue().getScoredNetwork(NETWORK_KEY_1)).isNotNull(); 457 } 458 459 @Test 460 public void scoreCacheUpdateScoresShouldTriggerOnAccessPointsChanged() 461 throws InterruptedException { 462 WifiTracker tracker = createMockedWifiTracker(); 463 startTracking(tracker); 464 sendScanResults(tracker); 465 466 updateScoresAndWaitForCacheListenerToProcess(tracker); 467 } 468 469 private void updateScoresAndWaitForCacheListenerToProcess(WifiTracker tracker) 470 throws InterruptedException { 471 // Scores are updated via the cache listener hence we need to wait for the work handler 472 // to finish before proceeding. 473 sendUpdatedScores(); 474 475 // Ensure the work handler has processed the scores inside the cache listener of WifiTracker 476 waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker); 477 } 478 479 @Test 480 public void scoreCacheUpdateScoresShouldChangeSortOrder() throws InterruptedException { 481 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 482 List<AccessPoint> aps = tracker.getAccessPoints(); 483 assertTrue(aps.size() == 2); 484 assertEquals(aps.get(0).getSsidStr(), SSID_1); 485 assertEquals(aps.get(1).getSsidStr(), SSID_2); 486 487 updateScoresAndWaitForCacheListenerToProcess(tracker); 488 489 aps = tracker.getAccessPoints(); 490 assertTrue(aps.size() == 2); 491 assertEquals(aps.get(0).getSsidStr(), SSID_2); 492 assertEquals(aps.get(1).getSsidStr(), SSID_1); 493 } 494 495 @Test 496 public void scoreCacheUpdateScoresShouldNotChangeSortOrderWhenSortingDisabled() 497 throws InterruptedException { 498 Settings.Global.putInt( 499 InstrumentationRegistry.getTargetContext().getContentResolver(), 500 Settings.Global.NETWORK_SCORING_UI_ENABLED, 501 0 /* disabled */); 502 503 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 504 List<AccessPoint> aps = tracker.getAccessPoints(); 505 assertTrue(aps.size() == 2); 506 assertEquals(aps.get(0).getSsidStr(), SSID_1); 507 assertEquals(aps.get(1).getSsidStr(), SSID_2); 508 509 updateScoresAndWaitForCacheListenerToProcess(tracker); 510 511 aps = tracker.getAccessPoints(); 512 assertTrue(aps.size() == 2); 513 assertEquals(aps.get(0).getSsidStr(), SSID_1); 514 assertEquals(aps.get(1).getSsidStr(), SSID_2); 515 } 516 517 @Test 518 public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint() 519 throws InterruptedException { 520 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 521 updateScoresAndWaitForCacheListenerToProcess(tracker); 522 523 List<AccessPoint> aps = tracker.getAccessPoints(); 524 525 for (AccessPoint ap : aps) { 526 if (ap.getSsidStr().equals(SSID_1)) { 527 assertEquals(BADGE_1, ap.getSpeed()); 528 } else if (ap.getSsidStr().equals(SSID_2)) { 529 assertEquals(BADGE_2, ap.getSpeed()); 530 } 531 } 532 } 533 534 @Test 535 public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering() 536 throws InterruptedException { 537 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 538 updateScoresAndWaitForCacheListenerToProcess(tracker); 539 540 List<AccessPoint> aps = tracker.getAccessPoints(); 541 542 for (AccessPoint ap : aps) { 543 if (ap.getSsidStr().equals(SSID_1)) { 544 assertFalse(ap.isMetered()); 545 } else if (ap.getSsidStr().equals(SSID_2)) { 546 assertTrue(ap.isMetered()); 547 } 548 } 549 } 550 551 @Test 552 public void noSpeedsShouldBeInsertedIntoAccessPointWhenScoringUiDisabled() 553 throws InterruptedException { 554 Settings.Global.putInt( 555 InstrumentationRegistry.getTargetContext().getContentResolver(), 556 Settings.Global.NETWORK_SCORING_UI_ENABLED, 557 0 /* disabled */); 558 559 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 560 updateScoresAndWaitForCacheListenerToProcess(tracker); 561 562 List<AccessPoint> aps = tracker.getAccessPoints(); 563 564 for (AccessPoint ap : aps) { 565 if (ap.getSsidStr().equals(SSID_1)) { 566 assertEquals(AccessPoint.Speed.NONE, ap.getSpeed()); 567 } else if (ap.getSsidStr().equals(SSID_2)) { 568 assertEquals(AccessPoint.Speed.NONE, ap.getSpeed()); 569 } 570 } 571 } 572 573 @Test 574 public void scoresShouldBeRequestedForNewScanResultOnly() throws InterruptedException { 575 // Scores can be requested together or serially depending on how the scan results are 576 // processed. 577 mRequestScoresLatch = new CountDownLatch(1); 578 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 579 assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 580 mRequestedKeys.clear(); 581 582 String ssid = "ssid3"; 583 String bssid = "00:00:00:00:00:00"; 584 ScanResult newResult = new ScanResult( 585 WifiSsid.createFromAsciiEncoded(ssid), 586 bssid, 587 0, // hessid 588 0, //anqpDomainId 589 null, // osuProviders 590 "", // capabilities 591 RSSI_1, 592 0, // frequency 593 SystemClock.elapsedRealtime() * 1000); 594 when(mockWifiManager.getScanResults()) 595 .thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2(), newResult)); 596 597 mRequestScoresLatch = new CountDownLatch(1); 598 sendScanResults(tracker); 599 assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 600 601 assertEquals(1, mRequestedKeys.size()); 602 assertTrue(mRequestedKeys.contains(new NetworkKey(new WifiKey('"' + ssid + '"', bssid)))); 603 } 604 605 @Test 606 public void scoreCacheAndListenerShouldBeUnregisteredWhenStopTrackingIsCalled() throws Exception 607 { 608 WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); 609 WifiNetworkScoreCache cache = mScoreCacheCaptor.getValue(); 610 611 tracker.onStop(); 612 verify(mockNetworkScoreManager).unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, cache); 613 614 // Verify listener is unregistered so updating a score does not throw an error by posting 615 // a message to the dead work handler 616 mWorkerThread.quit(); 617 sendUpdatedScores(); 618 } 619 620 /** 621 * Verify that tracking a Passpoint AP on a device with Passpoint disabled doesn't cause 622 * any crash. 623 * 624 * @throws Exception 625 */ 626 @Test 627 public void trackPasspointApWithPasspointDisabled() throws Exception { 628 // TODO(sghuman): Delete this test and replace with a passpoint test 629 WifiTracker tracker = createMockedWifiTracker(); 630 631 // Add a Passpoint AP to the scan results. 632 List<ScanResult> results = new ArrayList<>(); 633 ScanResult passpointAp = new ScanResult( 634 WifiSsid.createFromAsciiEncoded(SSID_1), 635 BSSID_1, 636 0, // hessid 637 0, //anqpDomainId 638 null, // osuProviders 639 "", // capabilities 640 RSSI_1, 641 0, // frequency 642 SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */); 643 passpointAp.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK); 644 results.add(passpointAp); 645 646 // Update access point and verify UnsupportedOperationException is being caught for 647 // call to WifiManager#getMatchingWifiConfig. 648 when(mockWifiManager.getConfiguredNetworks()) 649 .thenReturn(new ArrayList<WifiConfiguration>()); 650 when(mockWifiManager.getScanResults()).thenReturn(results); 651 652 startTracking(tracker); 653 } 654 655 @Test 656 public void rssiChangeBroadcastShouldUpdateConnectedAp() throws Exception { 657 WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected(); 658 assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue(); 659 660 int newRssi = CONNECTED_RSSI + 10; 661 WifiInfo info = new WifiInfo(CONNECTED_AP_1_INFO); 662 info.setRssi(newRssi); 663 664 // Once the new info has been fetched, we need to wait for the access points to be copied 665 mAccessPointsChangedLatch = new CountDownLatch(1); 666 doAnswer(invocation -> info).when(mockWifiManager).getConnectionInfo(); 667 668 tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.RSSI_CHANGED_ACTION)); 669 670 assertTrue("onAccessPointsChanged never called", 671 mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 672 assertThat(tracker.getAccessPoints().get(0).getRssi()).isEqualTo(newRssi); 673 } 674 675 @Test 676 public void onStartShouldSynchronouslyFetchLatestInformation() throws Exception { 677 Network mockNetwork = mock(Network.class); 678 when(mockWifiManager.getCurrentNetwork()).thenReturn(mockNetwork); 679 680 when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO); 681 682 NetworkInfo networkInfo = new NetworkInfo( 683 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype"); 684 networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test"); 685 when(mockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(networkInfo); 686 687 WifiTracker tracker = createMockedWifiTracker(); 688 startTracking(tracker); 689 690 verify(mockWifiManager).getConnectionInfo(); 691 verify(mockWifiManager, times(1)).getConfiguredNetworks(); 692 verify(mockConnectivityManager).getNetworkInfo(any(Network.class)); 693 694 // mStaleAccessPoints is true 695 verify(mockWifiListener, never()).onAccessPointsChanged(); 696 assertThat(tracker.getAccessPoints().size()).isEqualTo(2); 697 assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue(); 698 } 699 700 @Test 701 public void onStartShouldDisplayConnectedAccessPointWhenThereAreNoScanResults() 702 throws Exception { 703 Network mockNetwork = mock(Network.class); 704 when(mockWifiManager.getCurrentNetwork()).thenReturn(mockNetwork); 705 706 when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO); 707 708 NetworkInfo networkInfo = new NetworkInfo( 709 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype"); 710 networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test"); 711 when(mockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(networkInfo); 712 713 // Don't return any scan results 714 when(mockWifiManager.getScanResults()).thenReturn(new ArrayList<>()); 715 716 WifiTracker tracker = createMockedWifiTracker(); 717 startTracking(tracker); 718 719 verify(mockWifiManager).getConnectionInfo(); 720 verify(mockWifiManager, times(1)).getConfiguredNetworks(); 721 verify(mockConnectivityManager).getNetworkInfo(any(Network.class)); 722 723 // mStaleAccessPoints is true 724 verify(mockWifiListener, never()).onAccessPointsChanged(); 725 726 assertThat(tracker.getAccessPoints()).hasSize(1); 727 assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue(); 728 } 729 730 @Test 731 public void stopTrackingShouldRemoveAllPendingWork() throws Exception { 732 WifiTracker tracker = createMockedWifiTracker(); 733 startTracking(tracker); 734 735 CountDownLatch ready = new CountDownLatch(1); 736 CountDownLatch latch = new CountDownLatch(1); 737 CountDownLatch lock = new CountDownLatch(1); 738 tracker.mWorkHandler.post(() -> { 739 try { 740 ready.countDown(); 741 lock.await(); 742 latch.countDown(); 743 } catch (InterruptedException e) { 744 fail("Interrupted Exception while awaiting lock release: " + e); 745 } 746 }); 747 748 // Enqueue messages 749 final AtomicBoolean executed = new AtomicBoolean(false); 750 tracker.mWorkHandler.post(() -> executed.set(true)); 751 752 try { 753 ready.await(); // Make sure we have entered the first message handler 754 } catch (InterruptedException e) {} 755 tracker.onStop(); 756 757 lock.countDown(); 758 assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 759 760 // In case the method was already executing 761 assertThat(tracker.mWorkHandler.hasMessagesOrCallbacks()).isFalse(); 762 763 assertThat(executed.get()).isFalse(); 764 } 765 766 @Test 767 public void stopTrackingShouldPreventCallbacksFromOngoingWork() throws Exception { 768 WifiTracker tracker = createMockedWifiTracker(); 769 startTracking(tracker); 770 771 final CountDownLatch ready = new CountDownLatch(1); 772 final CountDownLatch latch = new CountDownLatch(1); 773 final CountDownLatch lock = new CountDownLatch(1); 774 tracker.mWorkHandler.post(() -> { 775 try { 776 ready.countDown(); 777 lock.await(); 778 779 tracker.mReceiver.onReceive( 780 mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION)); 781 782 latch.countDown(); 783 } catch (InterruptedException e) { 784 fail("Interrupted Exception while awaiting lock release: " + e); 785 } 786 }); 787 788 ready.await(); // Make sure we have entered the first message handler 789 tracker.onStop(); 790 lock.countDown(); 791 assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); 792 793 // Wait for main thread 794 final CountDownLatch latch2 = new CountDownLatch(1); 795 ThreadUtils.postOnMainThread(latch2::countDown); 796 latch2.await(); 797 798 verify(mockWifiListener, never()).onWifiStateChanged(anyInt()); 799 } 800 801 @Test 802 public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult() 803 throws Exception { 804 WifiTracker tracker = createMockedWifiTracker(); 805 startTracking(tracker); 806 807 tracker.onStop(); 808 809 startTracking(tracker); 810 811 tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION)); 812 tracker.mReceiver.onReceive( 813 mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION)); 814 tracker.mReceiver.onReceive( 815 mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION)); 816 817 818 verify(mockWifiListener, never()).onAccessPointsChanged(); 819 820 sendScanResults(tracker); // verifies onAccessPointsChanged is invoked 821 } 822 823 @Test 824 public void startTrackingShouldNotSendAnyCallbacksUntilScanResultsAreProcessed() 825 throws Exception { 826 WifiTracker tracker = createMockedWifiTracker(); 827 startTracking(tracker); 828 829 tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION)); 830 tracker.mReceiver.onReceive( 831 mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION)); 832 tracker.mReceiver.onReceive( 833 mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION)); 834 835 verify(mockWifiListener, never()).onAccessPointsChanged(); 836 837 sendScanResults(tracker); // verifies onAccessPointsChanged is invoked 838 } 839 840 @Test 841 public void disablingWifiShouldClearExistingAccessPoints() throws Exception { 842 WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected(); 843 844 when(mockWifiManager.isWifiEnabled()).thenReturn(false); 845 846 mAccessPointsChangedLatch = new CountDownLatch(1); 847 tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION)); 848 assertThat(mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)).isTrue(); 849 850 assertThat(tracker.getAccessPoints()).isEmpty(); 851 } 852 853 @Test 854 public void onConnectedChangedCallback_shouldNotBeInvokedWhenNoStateChange() throws Exception { 855 WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected(); 856 verify(mockWifiListener, times(1)).onConnectedChanged(); 857 858 NetworkInfo networkInfo = new NetworkInfo( 859 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype"); 860 networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test"); 861 862 Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); 863 intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); 864 tracker.mReceiver.onReceive(mContext, intent); 865 866 verify(mockWifiListener, times(1)).onConnectedChanged(); 867 } 868 869 @Test 870 public void onConnectedChangedCallback_shouldBeInvokedWhenStateChanges() throws Exception { 871 WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected(); 872 verify(mockWifiListener, times(1)).onConnectedChanged(); 873 874 NetworkInfo networkInfo = new NetworkInfo( 875 ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype"); 876 networkInfo.setDetailedState( 877 NetworkInfo.DetailedState.DISCONNECTED, "disconnected", "test"); 878 879 Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); 880 intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); 881 tracker.mReceiver.onReceive(mContext, intent); 882 883 assertThat(tracker.isConnected()).isFalse(); 884 verify(mockWifiListener, times(2)).onConnectedChanged(); 885 } 886 887 @Test 888 public void updateNetworkInfoWithNewConnectedNetwork_switchesNetworks() throws Exception { 889 WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected(); 890 891 switchToNetwork2(tracker); 892 893 List<AccessPoint> aps = tracker.getAccessPoints(); 894 assertThat(aps.get(0).getSsidStr()).isEqualTo(SSID_2); 895 896 assertThat(aps.get(0).isReachable()).isTrue(); 897 assertThat(aps.get(1).isReachable()).isTrue(); 898 } 899 } 900