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