Home | History | Annotate | Download | only in wifi
      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