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