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