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 17 package com.android.server.wifi; 18 19 import static com.android.server.wifi.WifiConfigurationTestUtil.generateWifiConfig; 20 import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE; 21 22 import static org.junit.Assert.*; 23 import static org.mockito.Mockito.*; 24 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.net.wifi.ScanResult; 28 import android.net.wifi.ScanResult.InformationElement; 29 import android.net.wifi.SupplicantState; 30 import android.net.wifi.WifiConfiguration; 31 import android.net.wifi.WifiInfo; 32 import android.net.wifi.WifiManager; 33 import android.net.wifi.WifiScanner; 34 import android.net.wifi.WifiScanner.PnoScanListener; 35 import android.net.wifi.WifiScanner.PnoSettings; 36 import android.net.wifi.WifiScanner.ScanListener; 37 import android.net.wifi.WifiScanner.ScanSettings; 38 import android.net.wifi.WifiSsid; 39 import android.os.SystemClock; 40 import android.os.WorkSource; 41 import android.test.suitebuilder.annotation.SmallTest; 42 43 import com.android.internal.R; 44 import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments; 45 46 import org.junit.After; 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.mockito.ArgumentCaptor; 50 51 import java.nio.charset.StandardCharsets; 52 import java.util.ArrayList; 53 import java.util.HashSet; 54 import java.util.concurrent.atomic.AtomicInteger; 55 56 /** 57 * Unit tests for {@link com.android.server.wifi.WifiConnectivityManager}. 58 */ 59 @SmallTest 60 public class WifiConnectivityManagerTest { 61 62 /** 63 * Called before each test 64 */ 65 @Before 66 public void setUp() throws Exception { 67 mWifiInjector = mockWifiInjector(); 68 mResource = mockResource(); 69 mAlarmManager = new MockAlarmManager(); 70 mContext = mockContext(); 71 mWifiStateMachine = mockWifiStateMachine(); 72 mWifiConfigManager = mockWifiConfigManager(); 73 mWifiInfo = getWifiInfo(); 74 mWifiScanner = mockWifiScanner(); 75 mWifiQNS = mockWifiQualifiedNetworkSelector(); 76 mWifiConnectivityManager = new WifiConnectivityManager(mContext, mWifiStateMachine, 77 mWifiScanner, mWifiConfigManager, mWifiInfo, mWifiQNS, mWifiInjector, 78 mLooper.getLooper()); 79 mWifiConnectivityManager.setWifiEnabled(true); 80 when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime()); 81 } 82 83 /** 84 * Called after each test 85 */ 86 @After 87 public void cleanup() { 88 validateMockitoUsage(); 89 } 90 91 private Resources mResource; 92 private Context mContext; 93 private MockAlarmManager mAlarmManager; 94 private MockLooper mLooper = new MockLooper(); 95 private WifiConnectivityManager mWifiConnectivityManager; 96 private WifiQualifiedNetworkSelector mWifiQNS; 97 private WifiStateMachine mWifiStateMachine; 98 private WifiScanner mWifiScanner; 99 private WifiConfigManager mWifiConfigManager; 100 private WifiInfo mWifiInfo; 101 private Clock mClock = mock(Clock.class); 102 private WifiLastResortWatchdog mWifiLastResortWatchdog; 103 private WifiMetrics mWifiMetrics; 104 private WifiInjector mWifiInjector; 105 106 private static final int CANDIDATE_NETWORK_ID = 0; 107 private static final String CANDIDATE_SSID = "\"AnSsid\""; 108 private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3"; 109 private static final String TAG = "WifiConnectivityManager Unit Test"; 110 private static final long CURRENT_SYSTEM_TIME_MS = 1000; 111 112 Resources mockResource() { 113 Resources resource = mock(Resources.class); 114 115 when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80); 116 when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24); 117 118 return resource; 119 } 120 121 Context mockContext() { 122 Context context = mock(Context.class); 123 124 when(context.getResources()).thenReturn(mResource); 125 when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn( 126 mAlarmManager.getAlarmManager()); 127 128 return context; 129 } 130 131 WifiScanner mockWifiScanner() { 132 WifiScanner scanner = mock(WifiScanner.class); 133 ArgumentCaptor<ScanListener> allSingleScanListenerCaptor = 134 ArgumentCaptor.forClass(ScanListener.class); 135 136 doNothing().when(scanner).registerScanListener(allSingleScanListenerCaptor.capture()); 137 138 // dummy scan results. QNS PeriodicScanListener bulids scanDetails from 139 // the fullScanResult and doesn't really use results 140 final WifiScanner.ScanData[] scanDatas = new WifiScanner.ScanData[1]; 141 142 // do a synchronous answer for the ScanListener callbacks 143 doAnswer(new AnswerWithArguments() { 144 public void answer(ScanSettings settings, ScanListener listener, 145 WorkSource workSource) throws Exception { 146 listener.onResults(scanDatas); 147 }}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject()); 148 149 doAnswer(new AnswerWithArguments() { 150 public void answer(ScanSettings settings, ScanListener listener, 151 WorkSource workSource) throws Exception { 152 listener.onResults(scanDatas); 153 allSingleScanListenerCaptor.getValue().onResults(scanDatas); 154 }}).when(scanner).startScan(anyObject(), anyObject(), anyObject()); 155 156 // This unfortunately needs to be a somewhat valid scan result, otherwise 157 // |ScanDetailUtil.toScanDetail| raises exceptions. 158 final ScanResult[] scanResults = new ScanResult[1]; 159 scanResults[0] = new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID), 160 CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps", 161 -78, 2450, 1025, 22, 33, 20, 0, 0, true); 162 scanResults[0].informationElements = new InformationElement[1]; 163 scanResults[0].informationElements[0] = new InformationElement(); 164 scanResults[0].informationElements[0].id = InformationElement.EID_SSID; 165 scanResults[0].informationElements[0].bytes = 166 CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8); 167 168 doAnswer(new AnswerWithArguments() { 169 public void answer(ScanSettings settings, PnoSettings pnoSettings, 170 PnoScanListener listener) throws Exception { 171 listener.onPnoNetworkFound(scanResults); 172 }}).when(scanner).startDisconnectedPnoScan(anyObject(), anyObject(), anyObject()); 173 174 doAnswer(new AnswerWithArguments() { 175 public void answer(ScanSettings settings, PnoSettings pnoSettings, 176 PnoScanListener listener) throws Exception { 177 listener.onPnoNetworkFound(scanResults); 178 }}).when(scanner).startConnectedPnoScan(anyObject(), anyObject(), anyObject()); 179 180 return scanner; 181 } 182 183 WifiStateMachine mockWifiStateMachine() { 184 WifiStateMachine stateMachine = mock(WifiStateMachine.class); 185 186 when(stateMachine.getFrequencyBand()).thenReturn(1); 187 when(stateMachine.isLinkDebouncing()).thenReturn(false); 188 when(stateMachine.isConnected()).thenReturn(false); 189 when(stateMachine.isDisconnected()).thenReturn(true); 190 when(stateMachine.isSupplicantTransientState()).thenReturn(false); 191 192 return stateMachine; 193 } 194 195 WifiQualifiedNetworkSelector mockWifiQualifiedNetworkSelector() { 196 WifiQualifiedNetworkSelector qns = mock(WifiQualifiedNetworkSelector.class); 197 198 WifiConfiguration candidate = generateWifiConfig( 199 0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null); 200 candidate.BSSID = CANDIDATE_BSSID; 201 ScanResult candidateScanResult = new ScanResult(); 202 candidateScanResult.SSID = CANDIDATE_SSID; 203 candidateScanResult.BSSID = CANDIDATE_BSSID; 204 candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult); 205 206 when(qns.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(), 207 anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(candidate); 208 return qns; 209 } 210 211 WifiInfo getWifiInfo() { 212 WifiInfo wifiInfo = new WifiInfo(); 213 214 wifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID); 215 wifiInfo.setBSSID(null); 216 wifiInfo.setSupplicantState(SupplicantState.DISCONNECTED); 217 218 return wifiInfo; 219 } 220 221 WifiConfigManager mockWifiConfigManager() { 222 WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class); 223 224 when(wifiConfigManager.getWifiConfiguration(anyInt())).thenReturn(null); 225 when(wifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true); 226 wifiConfigManager.mThresholdSaturatedRssi24 = new AtomicInteger( 227 WifiQualifiedNetworkSelector.RSSI_SATURATION_2G_BAND); 228 wifiConfigManager.mCurrentNetworkBoost = new AtomicInteger( 229 WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD); 230 231 // Pass dummy pno network list, otherwise Pno scan requests will not be triggered. 232 PnoSettings.PnoNetwork pnoNetwork = new PnoSettings.PnoNetwork(CANDIDATE_SSID); 233 ArrayList<PnoSettings.PnoNetwork> pnoNetworkList = new ArrayList<>(); 234 pnoNetworkList.add(pnoNetwork); 235 when(wifiConfigManager.retrieveDisconnectedPnoNetworkList()).thenReturn(pnoNetworkList); 236 when(wifiConfigManager.retrieveConnectedPnoNetworkList()).thenReturn(pnoNetworkList); 237 238 return wifiConfigManager; 239 } 240 241 WifiInjector mockWifiInjector() { 242 WifiInjector wifiInjector = mock(WifiInjector.class); 243 mWifiLastResortWatchdog = mock(WifiLastResortWatchdog.class); 244 mWifiMetrics = mock(WifiMetrics.class); 245 when(wifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog); 246 when(wifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics); 247 when(wifiInjector.getClock()).thenReturn(mClock); 248 return wifiInjector; 249 } 250 251 /** 252 * Wifi enters disconnected state while screen is on. 253 * 254 * Expected behavior: WifiConnectivityManager calls 255 * WifiStateMachine.autoConnectToNetwork() with the 256 * expected candidate network ID and BSSID. 257 */ 258 @Test 259 public void enterWifiDisconnectedStateWhenScreenOn() { 260 // Set screen to on 261 mWifiConnectivityManager.handleScreenStateChanged(true); 262 263 // Set WiFi to disconnected state 264 mWifiConnectivityManager.handleConnectionStateChanged( 265 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 266 267 verify(mWifiStateMachine).autoConnectToNetwork( 268 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 269 } 270 271 /** 272 * Wifi enters connected state while screen is on. 273 * 274 * Expected behavior: WifiConnectivityManager calls 275 * WifiStateMachine.autoConnectToNetwork() with the 276 * expected candidate network ID and BSSID. 277 */ 278 @Test 279 public void enterWifiConnectedStateWhenScreenOn() { 280 // Set screen to on 281 mWifiConnectivityManager.handleScreenStateChanged(true); 282 283 // Set WiFi to connected state 284 mWifiConnectivityManager.handleConnectionStateChanged( 285 WifiConnectivityManager.WIFI_STATE_CONNECTED); 286 287 verify(mWifiStateMachine).autoConnectToNetwork( 288 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 289 } 290 291 /** 292 * Screen turned on while WiFi in disconnected state. 293 * 294 * Expected behavior: WifiConnectivityManager calls 295 * WifiStateMachine.autoConnectToNetwork() with the 296 * expected candidate network ID and BSSID. 297 */ 298 @Test 299 public void turnScreenOnWhenWifiInDisconnectedState() { 300 // Set WiFi to disconnected state 301 mWifiConnectivityManager.handleConnectionStateChanged( 302 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 303 304 // Set screen to on 305 mWifiConnectivityManager.handleScreenStateChanged(true); 306 307 verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork( 308 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 309 } 310 311 /** 312 * Screen turned on while WiFi in connected state. 313 * 314 * Expected behavior: WifiConnectivityManager calls 315 * WifiStateMachine.autoConnectToNetwork() with the 316 * expected candidate network ID and BSSID. 317 */ 318 @Test 319 public void turnScreenOnWhenWifiInConnectedState() { 320 // Set WiFi to connected state 321 mWifiConnectivityManager.handleConnectionStateChanged( 322 WifiConnectivityManager.WIFI_STATE_CONNECTED); 323 324 // Set screen to on 325 mWifiConnectivityManager.handleScreenStateChanged(true); 326 327 verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork( 328 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 329 } 330 331 /** 332 * Screen turned on while WiFi in connected state but 333 * auto roaming is disabled. 334 * 335 * Expected behavior: WifiConnectivityManager doesn't invoke 336 * WifiStateMachine.autoConnectToNetwork() because roaming 337 * is turned off. 338 */ 339 @Test 340 public void turnScreenOnWhenWifiInConnectedStateRoamingDisabled() { 341 // Set WiFi to connected state 342 mWifiConnectivityManager.handleConnectionStateChanged( 343 WifiConnectivityManager.WIFI_STATE_CONNECTED); 344 345 // Turn off auto roaming 346 when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(false); 347 348 // Set screen to on 349 mWifiConnectivityManager.handleScreenStateChanged(true); 350 351 verify(mWifiStateMachine, times(0)).autoConnectToNetwork( 352 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 353 } 354 355 /** 356 * Multiple back to back connection attempts within the rate interval should be rate limited. 357 * 358 * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork() 359 * with the expected candidate network ID and BSSID for only the expected number of times within 360 * the given interval. 361 */ 362 @Test 363 public void connectionAttemptRateLimitedWhenScreenOff() { 364 int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE; 365 int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS; 366 int numAttempts = 0; 367 int connectionAttemptIntervals = timeInterval / maxAttemptRate; 368 369 mWifiConnectivityManager.handleScreenStateChanged(false); 370 371 // First attempt the max rate number of connections within the rate interval. 372 long currentTimeStamp = 0; 373 for (int attempt = 0; attempt < maxAttemptRate; attempt++) { 374 currentTimeStamp += connectionAttemptIntervals; 375 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 376 // Set WiFi to disconnected state to trigger PNO scan 377 mWifiConnectivityManager.handleConnectionStateChanged( 378 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 379 numAttempts++; 380 } 381 // Now trigger another connection attempt before the rate interval, this should be 382 // skipped because we've crossed rate limit. 383 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 384 // Set WiFi to disconnected state to trigger PNO scan 385 mWifiConnectivityManager.handleConnectionStateChanged( 386 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 387 388 // Verify that we attempt to connect upto the rate. 389 verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork( 390 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 391 } 392 393 /** 394 * Multiple back to back connection attempts outside the rate interval should not be rate 395 * limited. 396 * 397 * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork() 398 * with the expected candidate network ID and BSSID for only the expected number of times within 399 * the given interval. 400 */ 401 @Test 402 public void connectionAttemptNotRateLimitedWhenScreenOff() { 403 int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE; 404 int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS; 405 int numAttempts = 0; 406 int connectionAttemptIntervals = timeInterval / maxAttemptRate; 407 408 mWifiConnectivityManager.handleScreenStateChanged(false); 409 410 // First attempt the max rate number of connections within the rate interval. 411 long currentTimeStamp = 0; 412 for (int attempt = 0; attempt < maxAttemptRate; attempt++) { 413 currentTimeStamp += connectionAttemptIntervals; 414 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 415 // Set WiFi to disconnected state to trigger PNO scan 416 mWifiConnectivityManager.handleConnectionStateChanged( 417 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 418 numAttempts++; 419 } 420 // Now trigger another connection attempt after the rate interval, this should not be 421 // skipped because we should've evicted the older attempt. 422 when(mClock.elapsedRealtime()).thenReturn( 423 currentTimeStamp + connectionAttemptIntervals * 2); 424 // Set WiFi to disconnected state to trigger PNO scan 425 mWifiConnectivityManager.handleConnectionStateChanged( 426 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 427 numAttempts++; 428 429 // Verify that all the connection attempts went through 430 verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork( 431 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 432 } 433 434 /** 435 * Multiple back to back connection attempts after a user selection should not be rate limited. 436 * 437 * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork() 438 * with the expected candidate network ID and BSSID for only the expected number of times within 439 * the given interval. 440 */ 441 @Test 442 public void connectionAttemptNotRateLimitedWhenScreenOffAfterUserSelection() { 443 int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE; 444 int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS; 445 int numAttempts = 0; 446 int connectionAttemptIntervals = timeInterval / maxAttemptRate; 447 448 mWifiConnectivityManager.handleScreenStateChanged(false); 449 450 // First attempt the max rate number of connections within the rate interval. 451 long currentTimeStamp = 0; 452 for (int attempt = 0; attempt < maxAttemptRate; attempt++) { 453 currentTimeStamp += connectionAttemptIntervals; 454 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 455 // Set WiFi to disconnected state to trigger PNO scan 456 mWifiConnectivityManager.handleConnectionStateChanged( 457 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 458 numAttempts++; 459 } 460 461 mWifiConnectivityManager.connectToUserSelectNetwork(CANDIDATE_NETWORK_ID, false); 462 463 for (int attempt = 0; attempt < maxAttemptRate; attempt++) { 464 currentTimeStamp += connectionAttemptIntervals; 465 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 466 // Set WiFi to disconnected state to trigger PNO scan 467 mWifiConnectivityManager.handleConnectionStateChanged( 468 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 469 numAttempts++; 470 } 471 472 // Verify that all the connection attempts went through 473 verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork( 474 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 475 } 476 477 /** 478 * PNO retry for low RSSI networks. 479 * 480 * Expected behavior: WifiConnectivityManager doubles the low RSSI 481 * network retry delay value after QNS skips the PNO scan results 482 * because of their low RSSI values. 483 */ 484 @Test 485 public void PnoRetryForLowRssiNetwork() { 486 when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(), 487 anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null); 488 489 // Set screen to off 490 mWifiConnectivityManager.handleScreenStateChanged(false); 491 492 // Get the current retry delay value 493 int lowRssiNetworkRetryDelayStartValue = mWifiConnectivityManager 494 .getLowRssiNetworkRetryDelay(); 495 496 // Set WiFi to disconnected state to trigger PNO scan 497 mWifiConnectivityManager.handleConnectionStateChanged( 498 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 499 500 // Get the retry delay value after QNS didn't select a 501 // network candicate from the PNO scan results. 502 int lowRssiNetworkRetryDelayAfterPnoValue = mWifiConnectivityManager 503 .getLowRssiNetworkRetryDelay(); 504 505 assertEquals(lowRssiNetworkRetryDelayStartValue * 2, 506 lowRssiNetworkRetryDelayAfterPnoValue); 507 } 508 509 /** 510 * Ensure that the watchdog bite increments the "Pno bad" metric. 511 * 512 * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find 513 * a candidate while watchdog single scan did. 514 */ 515 @Test 516 public void watchdogBitePnoBadIncrementsMetrics() { 517 // Set screen to off 518 mWifiConnectivityManager.handleScreenStateChanged(false); 519 520 // Set WiFi to disconnected state to trigger PNO scan 521 mWifiConnectivityManager.handleConnectionStateChanged( 522 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 523 524 // Now fire the watchdog alarm and verify the metrics were incremented. 525 mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG); 526 mLooper.dispatchAll(); 527 528 verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoBad(); 529 verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoGood(); 530 } 531 532 /** 533 * Ensure that the watchdog bite increments the "Pno good" metric. 534 * 535 * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find 536 * a candidate which was the same with watchdog single scan. 537 */ 538 @Test 539 public void watchdogBitePnoGoodIncrementsMetrics() { 540 // Qns returns no candidate after watchdog single scan. 541 when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(), 542 anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null); 543 544 // Set screen to off 545 mWifiConnectivityManager.handleScreenStateChanged(false); 546 547 // Set WiFi to disconnected state to trigger PNO scan 548 mWifiConnectivityManager.handleConnectionStateChanged( 549 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 550 551 // Now fire the watchdog alarm and verify the metrics were incremented. 552 mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG); 553 mLooper.dispatchAll(); 554 555 verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoGood(); 556 verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoBad(); 557 } 558 559 /** 560 * Verify that scan interval for screen on and wifi disconnected scenario 561 * is in the exponential backoff fashion. 562 * 563 * Expected behavior: WifiConnectivityManager doubles periodic 564 * scan interval. 565 */ 566 @Test 567 public void checkPeriodicScanIntervalWhenDisconnected() { 568 long currentTimeStamp = CURRENT_SYSTEM_TIME_MS; 569 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 570 571 // Set screen to ON 572 mWifiConnectivityManager.handleScreenStateChanged(true); 573 574 // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered 575 // by screen state change can settle 576 currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS; 577 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 578 579 // Set WiFi to disconnected state to trigger periodic scan 580 mWifiConnectivityManager.handleConnectionStateChanged( 581 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 582 583 // Get the first periodic scan interval 584 long firstIntervalMs = mAlarmManager 585 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG) 586 - currentTimeStamp; 587 assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS); 588 589 currentTimeStamp += firstIntervalMs; 590 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 591 592 // Now fire the first periodic scan alarm timer 593 mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG); 594 mLooper.dispatchAll(); 595 596 // Get the second periodic scan interval 597 long secondIntervalMs = mAlarmManager 598 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG) 599 - currentTimeStamp; 600 601 // Verify the intervals are exponential back off 602 assertEquals(firstIntervalMs * 2, secondIntervalMs); 603 604 currentTimeStamp += secondIntervalMs; 605 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 606 607 // Make sure we eventually stay at the maximum scan interval. 608 long intervalMs = 0; 609 for (int i = 0; i < 5; i++) { 610 mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG); 611 mLooper.dispatchAll(); 612 intervalMs = mAlarmManager 613 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG) 614 - currentTimeStamp; 615 currentTimeStamp += intervalMs; 616 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 617 } 618 619 assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS); 620 } 621 622 /** 623 * Verify that scan interval for screen on and wifi connected scenario 624 * is in the exponential backoff fashion. 625 * 626 * Expected behavior: WifiConnectivityManager doubles periodic 627 * scan interval. 628 */ 629 @Test 630 public void checkPeriodicScanIntervalWhenConnected() { 631 long currentTimeStamp = CURRENT_SYSTEM_TIME_MS; 632 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 633 634 // Set screen to ON 635 mWifiConnectivityManager.handleScreenStateChanged(true); 636 637 // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered 638 // by screen state change can settle 639 currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS; 640 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 641 642 // Set WiFi to connected state to trigger periodic scan 643 mWifiConnectivityManager.handleConnectionStateChanged( 644 WifiConnectivityManager.WIFI_STATE_CONNECTED); 645 646 // Get the first periodic scan interval 647 long firstIntervalMs = mAlarmManager 648 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG) 649 - currentTimeStamp; 650 assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS); 651 652 currentTimeStamp += firstIntervalMs; 653 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 654 655 // Now fire the first periodic scan alarm timer 656 mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG); 657 mLooper.dispatchAll(); 658 659 // Get the second periodic scan interval 660 long secondIntervalMs = mAlarmManager 661 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG) 662 - currentTimeStamp; 663 664 // Verify the intervals are exponential back off 665 assertEquals(firstIntervalMs * 2, secondIntervalMs); 666 667 currentTimeStamp += secondIntervalMs; 668 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 669 670 // Make sure we eventually stay at the maximum scan interval. 671 long intervalMs = 0; 672 for (int i = 0; i < 5; i++) { 673 mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG); 674 mLooper.dispatchAll(); 675 intervalMs = mAlarmManager 676 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG) 677 - currentTimeStamp; 678 currentTimeStamp += intervalMs; 679 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 680 } 681 682 assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS); 683 } 684 685 /** 686 * When screen on trigger two connection state change events back to back to 687 * verify that the minium scan interval is enforced. 688 * 689 * Expected behavior: WifiConnectivityManager start the second periodic single 690 * scan PERIODIC_SCAN_INTERVAL_MS after the first one. 691 */ 692 @Test 693 public void checkMinimumPeriodicScanIntervalWhenScreenOn() { 694 long currentTimeStamp = CURRENT_SYSTEM_TIME_MS; 695 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 696 697 // Set screen to ON 698 mWifiConnectivityManager.handleScreenStateChanged(true); 699 700 // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered 701 // by screen state change can settle 702 currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS; 703 long firstScanTimeStamp = currentTimeStamp; 704 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 705 706 // Set WiFi to connected state to trigger the periodic scan 707 mWifiConnectivityManager.handleConnectionStateChanged( 708 WifiConnectivityManager.WIFI_STATE_CONNECTED); 709 710 // Set the second scan attempt time stamp. 711 currentTimeStamp += 2000; 712 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 713 714 // Set WiFi to disconnected state to trigger another periodic scan 715 mWifiConnectivityManager.handleConnectionStateChanged( 716 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 717 718 // Get the second periodic scan actual time stamp 719 long secondScanTimeStamp = mAlarmManager 720 .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG); 721 722 // Verify that the second scan is scheduled PERIODIC_SCAN_INTERVAL_MS after the 723 // very first scan. 724 assertEquals(secondScanTimeStamp, firstScanTimeStamp 725 + WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS); 726 727 } 728 729 /** 730 * When screen on trigger a connection state change event and a forced connectivity 731 * scan event back to back to verify that the minimum scan interval is not applied 732 * in this scenario. 733 * 734 * Expected behavior: WifiConnectivityManager starts the second periodic single 735 * scan immediately. 736 */ 737 @Test 738 public void checkMinimumPeriodicScanIntervalNotEnforced() { 739 long currentTimeStamp = CURRENT_SYSTEM_TIME_MS; 740 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 741 742 // Set screen to ON 743 mWifiConnectivityManager.handleScreenStateChanged(true); 744 745 // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered 746 // by screen state change can settle 747 currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS; 748 long firstScanTimeStamp = currentTimeStamp; 749 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 750 751 // Set WiFi to connected state to trigger the periodic scan 752 mWifiConnectivityManager.handleConnectionStateChanged( 753 WifiConnectivityManager.WIFI_STATE_CONNECTED); 754 755 // Set the second scan attempt time stamp 756 currentTimeStamp += 2000; 757 when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp); 758 759 // Force a connectivity scan 760 mWifiConnectivityManager.forceConnectivityScan(); 761 762 // Get the second periodic scan actual time stamp. Note, this scan is not 763 // started from the AlarmManager. 764 long secondScanTimeStamp = mWifiConnectivityManager.getLastPeriodicSingleScanTimeStamp(); 765 766 // Verify that the second scan is fired immediately 767 assertEquals(secondScanTimeStamp, currentTimeStamp); 768 } 769 770 /** 771 * Verify that we perform full band scan when the currently connected network's tx/rx success 772 * rate is low. 773 * 774 * Expected behavior: WifiConnectivityManager does full band scan. 775 */ 776 @Test 777 public void checkSingleScanSettingsWhenConnectedWithLowDataRate() { 778 mWifiInfo.txSuccessRate = 0; 779 mWifiInfo.rxSuccessRate = 0; 780 781 final HashSet<Integer> channelList = new HashSet<>(); 782 channelList.add(1); 783 channelList.add(2); 784 channelList.add(3); 785 786 when(mWifiStateMachine.getCurrentWifiConfiguration()) 787 .thenReturn(new WifiConfiguration()); 788 when(mWifiStateMachine.getFrequencyBand()) 789 .thenReturn(WifiManager.WIFI_FREQUENCY_BAND_5GHZ); 790 when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt())) 791 .thenReturn(channelList); 792 793 doAnswer(new AnswerWithArguments() { 794 public void answer(ScanSettings settings, ScanListener listener, 795 WorkSource workSource) throws Exception { 796 assertEquals(settings.band, WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS); 797 assertNull(settings.channels); 798 }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject()); 799 800 // Set screen to ON 801 mWifiConnectivityManager.handleScreenStateChanged(true); 802 803 // Set WiFi to connected state to trigger periodic scan 804 mWifiConnectivityManager.handleConnectionStateChanged( 805 WifiConnectivityManager.WIFI_STATE_CONNECTED); 806 807 verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject()); 808 } 809 810 /** 811 * Verify that we perform partial scan when the currently connected network's tx/rx success 812 * rate is high and when the currently connected network is present in scan 813 * cache in WifiConfigManager. 814 * 815 * Expected behavior: WifiConnectivityManager does full band scan. 816 */ 817 @Test 818 public void checkSingleScanSettingsWhenConnectedWithHighDataRate() { 819 mWifiInfo.txSuccessRate = WifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2; 820 mWifiInfo.rxSuccessRate = WifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2; 821 822 final HashSet<Integer> channelList = new HashSet<>(); 823 channelList.add(1); 824 channelList.add(2); 825 channelList.add(3); 826 827 when(mWifiStateMachine.getCurrentWifiConfiguration()) 828 .thenReturn(new WifiConfiguration()); 829 when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt())) 830 .thenReturn(channelList); 831 832 doAnswer(new AnswerWithArguments() { 833 public void answer(ScanSettings settings, ScanListener listener, 834 WorkSource workSource) throws Exception { 835 assertEquals(settings.band, WifiScanner.WIFI_BAND_UNSPECIFIED); 836 assertEquals(settings.channels.length, channelList.size()); 837 for (int chanIdx = 0; chanIdx < settings.channels.length; chanIdx++) { 838 assertTrue(channelList.contains(settings.channels[chanIdx].frequency)); 839 } 840 }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject()); 841 842 // Set screen to ON 843 mWifiConnectivityManager.handleScreenStateChanged(true); 844 845 // Set WiFi to connected state to trigger periodic scan 846 mWifiConnectivityManager.handleConnectionStateChanged( 847 WifiConnectivityManager.WIFI_STATE_CONNECTED); 848 849 verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject()); 850 } 851 852 /** 853 * Verify that we fall back to full band scan when the currently connected network's tx/rx 854 * success rate is high and the currently connected network is not present in scan cache in 855 * WifiConfigManager. This is simulated by returning an empty hashset in |makeChannelList|. 856 * 857 * Expected behavior: WifiConnectivityManager does full band scan. 858 */ 859 @Test 860 public void checkSingleScanSettingsWhenConnectedWithHighDataRateNotInCache() { 861 mWifiInfo.txSuccessRate = WifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2; 862 mWifiInfo.rxSuccessRate = WifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2; 863 864 final HashSet<Integer> channelList = new HashSet<>(); 865 866 when(mWifiStateMachine.getCurrentWifiConfiguration()) 867 .thenReturn(new WifiConfiguration()); 868 when(mWifiStateMachine.getFrequencyBand()) 869 .thenReturn(WifiManager.WIFI_FREQUENCY_BAND_5GHZ); 870 when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt())) 871 .thenReturn(channelList); 872 873 doAnswer(new AnswerWithArguments() { 874 public void answer(ScanSettings settings, ScanListener listener, 875 WorkSource workSource) throws Exception { 876 assertEquals(settings.band, WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS); 877 assertNull(settings.channels); 878 }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject()); 879 880 // Set screen to ON 881 mWifiConnectivityManager.handleScreenStateChanged(true); 882 883 // Set WiFi to connected state to trigger periodic scan 884 mWifiConnectivityManager.handleConnectionStateChanged( 885 WifiConnectivityManager.WIFI_STATE_CONNECTED); 886 887 verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject()); 888 } 889 890 /** 891 * Verify that we retry connectivity scan up to MAX_SCAN_RESTART_ALLOWED times 892 * when Wifi somehow gets into a bad state and fails to scan. 893 * 894 * Expected behavior: WifiConnectivityManager schedules connectivity scan 895 * MAX_SCAN_RESTART_ALLOWED times. 896 */ 897 @Test 898 public void checkMaximumScanRetry() { 899 // Set screen to ON 900 mWifiConnectivityManager.handleScreenStateChanged(true); 901 902 doAnswer(new AnswerWithArguments() { 903 public void answer(ScanSettings settings, ScanListener listener, 904 WorkSource workSource) throws Exception { 905 listener.onFailure(-1, "ScanFailure"); 906 }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject()); 907 908 // Set WiFi to disconnected state to trigger the single scan based periodic scan 909 mWifiConnectivityManager.handleConnectionStateChanged( 910 WifiConnectivityManager.WIFI_STATE_DISCONNECTED); 911 912 // Fire the alarm timer 2x timers 913 for (int i = 0; i < (WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED * 2); i++) { 914 mAlarmManager.dispatch(WifiConnectivityManager.RESTART_SINGLE_SCAN_TIMER_TAG); 915 mLooper.dispatchAll(); 916 } 917 918 // Verify that the connectivity scan has been retried for MAX_SCAN_RESTART_ALLOWED 919 // times. Note, WifiScanner.startScan() is invoked MAX_SCAN_RESTART_ALLOWED + 1 times. 920 // The very first scan is the initial one, and the other MAX_SCAN_RESTART_ALLOWED 921 // are the retrial ones. 922 verify(mWifiScanner, times(WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED + 1)).startScan( 923 anyObject(), anyObject(), anyObject()); 924 } 925 926 /** 927 * Listen to scan results not requested by WifiConnectivityManager and 928 * act on them. 929 * 930 * Expected behavior: WifiConnectivityManager calls 931 * WifiStateMachine.autoConnectToNetwork() with the 932 * expected candidate network ID and BSSID. 933 */ 934 @Test 935 public void listenToAllSingleScanResults() { 936 ScanSettings settings = new ScanSettings(); 937 ScanListener scanListener = mock(ScanListener.class); 938 939 // Request a single scan outside of WifiConnectivityManager. 940 mWifiScanner.startScan(settings, scanListener, WIFI_WORK_SOURCE); 941 942 // Verify that WCM receives the scan results and initiates a connection 943 // to the network. 944 verify(mWifiStateMachine).autoConnectToNetwork( 945 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID); 946 } 947 } 948