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