1 /* 2 * Copyright (C) 2019 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.wifitrackerlib; 18 19 import static android.net.wifi.WifiInfo.SECURITY_TYPE_PSK; 20 21 import static com.android.wifitrackerlib.StandardWifiEntry.StandardWifiEntryKey; 22 import static com.android.wifitrackerlib.StandardWifiEntry.ssidAndSecurityTypeToStandardWifiEntryKey; 23 import static com.android.wifitrackerlib.TestUtils.BAD_LEVEL; 24 import static com.android.wifitrackerlib.TestUtils.BAD_RSSI; 25 import static com.android.wifitrackerlib.TestUtils.GOOD_RSSI; 26 import static com.android.wifitrackerlib.TestUtils.buildScanResult; 27 import static com.android.wifitrackerlib.TestUtils.buildWifiConfiguration; 28 import static com.android.wifitrackerlib.WifiEntry.SECURITY_NONE; 29 import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; 30 31 import static com.google.common.truth.Truth.assertThat; 32 33 import static org.mockito.ArgumentMatchers.any; 34 import static org.mockito.Mockito.doAnswer; 35 import static org.mockito.Mockito.mock; 36 import static org.mockito.Mockito.spy; 37 import static org.mockito.Mockito.verify; 38 import static org.mockito.Mockito.when; 39 40 import android.content.BroadcastReceiver; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.res.Resources; 44 import android.net.ConnectivityDiagnosticsManager; 45 import android.net.ConnectivityManager; 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.WifiScanner; 51 import android.os.Handler; 52 import android.os.test.TestLooper; 53 54 import androidx.lifecycle.Lifecycle; 55 import androidx.lifecycle.LifecycleObserver; 56 57 import org.junit.Before; 58 import org.junit.Test; 59 import org.mockito.ArgumentCaptor; 60 import org.mockito.Mock; 61 import org.mockito.MockitoAnnotations; 62 import org.mockito.stubbing.Answer; 63 64 import java.time.Clock; 65 import java.util.ArrayList; 66 import java.util.Collections; 67 68 public class StandardNetworkDetailsTrackerTest { 69 70 private static final long START_MILLIS = 123_456_789; 71 72 private static final long MAX_SCAN_AGE_MILLIS = 15_000; 73 private static final long SCAN_INTERVAL_MILLIS = 10_000; 74 75 @Mock private WifiTrackerInjector mInjector; 76 @Mock private Lifecycle mMockLifecycle; 77 @Mock private Context mMockContext; 78 @Mock private Resources mResources; 79 @Mock private WifiManager mMockWifiManager; 80 @Mock private WifiScanner mWifiScanner; 81 @Mock private ConnectivityManager mMockConnectivityManager; 82 @Mock private ConnectivityDiagnosticsManager mMockConnectivityDiagnosticsManager; 83 @Mock private Clock mMockClock; 84 85 private TestLooper mTestLooper; 86 private Handler mMainHandler; 87 private Handler mWorkerHandler; 88 89 private final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor = 90 ArgumentCaptor.forClass(BroadcastReceiver.class); 91 createTestStandardNetworkDetailsTracker( String key)92 private StandardNetworkDetailsTracker createTestStandardNetworkDetailsTracker( 93 String key) { 94 95 return new StandardNetworkDetailsTracker( 96 mInjector, 97 mMockLifecycle, 98 mMockContext, 99 mMockWifiManager, 100 mMockConnectivityManager, 101 mMainHandler, 102 mWorkerHandler, 103 mMockClock, 104 MAX_SCAN_AGE_MILLIS, 105 SCAN_INTERVAL_MILLIS, 106 key); 107 } 108 109 @Before setUp()110 public void setUp() { 111 MockitoAnnotations.initMocks(this); 112 113 mTestLooper = new TestLooper(); 114 mMainHandler = spy(new Handler(mTestLooper.getLooper())); 115 mWorkerHandler = spy(new Handler(mTestLooper.getLooper())); 116 117 when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true); 118 when(mMockWifiManager.isWpa3SuiteBSupported()).thenReturn(true); 119 when(mMockWifiManager.isEnhancedOpenSupported()).thenReturn(true); 120 when(mMockWifiManager.getScanResults()).thenReturn(new ArrayList<>()); 121 when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); 122 when(mMockWifiManager.calculateSignalLevel(TestUtils.GOOD_RSSI)) 123 .thenReturn(TestUtils.GOOD_LEVEL); 124 when(mMockWifiManager.calculateSignalLevel(TestUtils.OKAY_RSSI)) 125 .thenReturn(TestUtils.OKAY_LEVEL); 126 when(mMockWifiManager.calculateSignalLevel(TestUtils.BAD_RSSI)) 127 .thenReturn(TestUtils.BAD_LEVEL); 128 when(mMockContext.getResources()).thenReturn(mResources); 129 when(mMockContext.getSystemService(ConnectivityDiagnosticsManager.class)) 130 .thenReturn(mMockConnectivityDiagnosticsManager); 131 when(mMockContext.getSystemService(WifiScanner.class)).thenReturn(mWifiScanner); 132 when(mMockClock.millis()).thenReturn(START_MILLIS); 133 } 134 135 /** 136 * Tests that the key of the created WifiEntry matches the key passed into the constructor. 137 */ 138 @Test testGetWifiEntry_HasCorrectKey()139 public void testGetWifiEntry_HasCorrectKey() throws Exception { 140 final StandardWifiEntryKey key = 141 ssidAndSecurityTypeToStandardWifiEntryKey("ssid", SECURITY_NONE); 142 143 final StandardNetworkDetailsTracker tracker = 144 createTestStandardNetworkDetailsTracker(key.toString()); 145 146 assertThat(tracker.getWifiEntry().getKey()).isEqualTo(key.toString()); 147 } 148 149 /** 150 * Tests that the key of the created WifiEntry matches the key passed into the constructor. 151 */ 152 @Test testConstructor_lifecycleCallbacksWhenRegistering_populatesChosenEntry()153 public void testConstructor_lifecycleCallbacksWhenRegistering_populatesChosenEntry() 154 throws Exception { 155 doAnswer(invocation -> { 156 BaseWifiTracker.WifiTrackerLifecycleObserver observer = invocation.getArgument(0); 157 observer.onStart(); 158 return null; 159 }).when(mMockLifecycle).addObserver(any(LifecycleObserver.class)); 160 when(mWorkerHandler.post(any(Runnable.class))) 161 .thenAnswer((Answer<Boolean>) invocation -> { 162 Runnable runnable = invocation.getArgument(0); 163 runnable.run(); 164 return true; 165 }); 166 final StandardWifiEntryKey key = 167 ssidAndSecurityTypeToStandardWifiEntryKey("ssid", SECURITY_NONE); 168 169 final StandardNetworkDetailsTracker tracker = 170 createTestStandardNetworkDetailsTracker(key.toString()); 171 172 assertThat(tracker.getWifiEntry().getKey()).isEqualTo(key.toString()); 173 } 174 175 /** 176 * Tests that SCAN_RESULTS_AVAILABLE_ACTION updates the level of the entry. 177 */ 178 @Test testScanResultsAvailableAction_updates_getLevel()179 public void testScanResultsAvailableAction_updates_getLevel() throws Exception { 180 // Starting without any scans available should make level WIFI_LEVEL_UNREACHABLE 181 final ScanResult scan = buildScanResult("ssid", "bssid", START_MILLIS, -50 /* rssi */); 182 final StandardWifiEntryKey key = 183 ssidAndSecurityTypeToStandardWifiEntryKey("ssid", SECURITY_NONE); 184 final StandardNetworkDetailsTracker tracker = 185 createTestStandardNetworkDetailsTracker(key.toString()); 186 187 tracker.onStart(); 188 mTestLooper.dispatchAll(); 189 verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(), 190 any(), any(), any()); 191 final WifiEntry wifiEntry = tracker.getWifiEntry(); 192 193 assertThat(wifiEntry.getLevel()).isEqualTo(WIFI_LEVEL_UNREACHABLE); 194 195 // Received fresh scan. Level should not be WIFI_LEVEL_UNREACHABLE anymore 196 when(mMockWifiManager.getScanResults()).thenReturn(Collections.singletonList(scan)); 197 198 mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, 199 new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) 200 .putExtra(WifiManager.EXTRA_RESULTS_UPDATED, true)); 201 202 assertThat(wifiEntry.getLevel()).isNotEqualTo(WIFI_LEVEL_UNREACHABLE); 203 204 // Scan returned with no scans, old scans timed out. Level should be WIFI_LEVEL_UNREACHABLE. 205 when(mMockWifiManager.getScanResults()).thenReturn(Collections.emptyList()); 206 when(mMockClock.millis()).thenReturn(START_MILLIS + MAX_SCAN_AGE_MILLIS + 1); 207 208 mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, 209 new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) 210 .putExtra(WifiManager.EXTRA_RESULTS_UPDATED, true)); 211 212 assertThat(wifiEntry.getLevel()).isEqualTo(WIFI_LEVEL_UNREACHABLE); 213 } 214 215 /** 216 * Tests that CONFIGURED_NETWORKS_CHANGED_ACTION updates the isSaved() value of the entry. 217 */ 218 @Test testConfiguredNetworksChangedAction_updates_isSaved()219 public void testConfiguredNetworksChangedAction_updates_isSaved() throws Exception { 220 // Initialize with no config. isSaved() should return false. 221 final StandardWifiEntryKey key = 222 ssidAndSecurityTypeToStandardWifiEntryKey("ssid", SECURITY_NONE); 223 final StandardNetworkDetailsTracker tracker = 224 createTestStandardNetworkDetailsTracker(key.toString()); 225 226 tracker.onStart(); 227 mTestLooper.dispatchAll(); 228 verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(), 229 any(), any(), any()); 230 final WifiEntry wifiEntry = tracker.getWifiEntry(); 231 232 assertThat(wifiEntry.isSaved()).isFalse(); 233 234 // Add a config and send a broadcast. isSaved() should return true. 235 final WifiConfiguration config = new WifiConfiguration(); 236 config.SSID = "\"" + "ssid" + "\""; 237 when(mMockWifiManager.getPrivilegedConfiguredNetworks()) 238 .thenReturn(Collections.singletonList(config)); 239 mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, 240 new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION)); 241 242 assertThat(wifiEntry.isSaved()).isTrue(); 243 244 // Remove the config and send a broadcast. isSaved() should be false. 245 when(mMockWifiManager.getPrivilegedConfiguredNetworks()) 246 .thenReturn(Collections.emptyList()); 247 mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, 248 new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION)); 249 250 assertThat(wifiEntry.isSaved()).isFalse(); 251 } 252 253 /** 254 * Tests that WIFI_STATE_DISABLED will clear the scan results of the chosen entry regardless if 255 * the scan results are still valid. 256 */ 257 @Test testWifiStateChanged_disabled_clearsLevel()258 public void testWifiStateChanged_disabled_clearsLevel() throws Exception { 259 // Start with scan result and wifi state enabled. Level should not be unreachable. 260 final ScanResult scan = buildScanResult("ssid", "bssid", START_MILLIS, -50 /* rssi */); 261 final StandardWifiEntryKey key = 262 ssidAndSecurityTypeToStandardWifiEntryKey("ssid", SECURITY_NONE); 263 when(mMockWifiManager.getScanResults()).thenReturn(Collections.singletonList(scan)); 264 265 final StandardNetworkDetailsTracker tracker = 266 createTestStandardNetworkDetailsTracker(key.toString()); 267 tracker.onStart(); 268 mTestLooper.dispatchAll(); 269 verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(), 270 any(), any(), any()); 271 final WifiEntry wifiEntry = tracker.getWifiEntry(); 272 273 assertThat(wifiEntry.getLevel()).isNotEqualTo(WIFI_LEVEL_UNREACHABLE); 274 275 // Disable wifi. Level should be unreachable. 276 when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED); 277 278 mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, 279 new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION)); 280 281 assertThat(wifiEntry.getLevel()).isEqualTo(WIFI_LEVEL_UNREACHABLE); 282 } 283 284 @Test testSecurityTargeting_pskScansWithSaeConfig_correspondsToNewNetworkTargeting()285 public void testSecurityTargeting_pskScansWithSaeConfig_correspondsToNewNetworkTargeting() { 286 final String ssid = "ssid"; 287 final WifiConfiguration config = buildWifiConfiguration(ssid); 288 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); 289 when(mMockWifiManager.getPrivilegedConfiguredNetworks()) 290 .thenReturn(Collections.singletonList(config)); 291 final ScanResult scan = buildScanResult(ssid, "bssid", START_MILLIS, -50 /* rssi */); 292 scan.capabilities = "[PSK]"; 293 when(mMockWifiManager.getScanResults()).thenReturn(Collections.singletonList(scan)); 294 295 // Start without targeting new networks 296 StandardNetworkDetailsTracker tracker = createTestStandardNetworkDetailsTracker( 297 ssidAndSecurityTypeToStandardWifiEntryKey(ssid, SECURITY_TYPE_PSK).toString()); 298 tracker.onStart(); 299 mTestLooper.dispatchAll(); 300 301 // WifiEntry should correspond to the saved config 302 WifiEntry wifiEntry = tracker.getWifiEntry(); 303 assertThat(wifiEntry.getSecurityTypes().get(0)).isEqualTo(WifiInfo.SECURITY_TYPE_SAE); 304 assertThat(wifiEntry.getLevel()).isEqualTo(WIFI_LEVEL_UNREACHABLE); 305 306 // Now target new networks as if we got the key from WifiPickerTracker 307 tracker = createTestStandardNetworkDetailsTracker( 308 ssidAndSecurityTypeToStandardWifiEntryKey(ssid, SECURITY_TYPE_PSK, 309 true /* isTargetingNewNetworks */).toString()); 310 tracker.onStart(); 311 mTestLooper.dispatchAll(); 312 313 // WifiEntry should correspond to the unsaved scan 314 wifiEntry = tracker.getWifiEntry(); 315 assertThat(wifiEntry.getSecurityTypes().get(0)).isEqualTo(SECURITY_TYPE_PSK); 316 assertThat(wifiEntry.getLevel()).isNotEqualTo(WIFI_LEVEL_UNREACHABLE); 317 } 318 319 /** 320 * Tests that we update the chosen entry's ScanResults correctly after a WifiScanner scan. 321 */ 322 @Test testScanner_wifiScannerResultReceived_scanResultsUpdated()323 public void testScanner_wifiScannerResultReceived_scanResultsUpdated() { 324 final String ssid = "ssid"; 325 final WifiConfiguration config = buildWifiConfiguration(ssid); 326 when(mMockWifiManager.getPrivilegedConfiguredNetworks()) 327 .thenReturn(Collections.singletonList(config)); 328 StandardNetworkDetailsTracker tracker = createTestStandardNetworkDetailsTracker( 329 ssidAndSecurityTypeToStandardWifiEntryKey(ssid, SECURITY_NONE).toString()); 330 tracker.onStart(); 331 mTestLooper.dispatchAll(); 332 verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(), 333 any(), any(), any()); 334 mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, 335 new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION).putExtra( 336 WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED)); 337 ArgumentCaptor<WifiScanner.ScanListener> mScanListenerCaptor = 338 ArgumentCaptor.forClass(WifiScanner.ScanListener.class); 339 verify(mWifiScanner).startScan(any(), mScanListenerCaptor.capture()); 340 ScanResult[] scanResults = new ScanResult[]{ 341 buildScanResult(ssid, "bssid", START_MILLIS, BAD_RSSI), 342 buildScanResult("different ssid", "bssid", START_MILLIS, GOOD_RSSI), 343 }; 344 WifiScanner.ScanData scanData = mock(WifiScanner.ScanData.class); 345 when(scanData.getResults()).thenReturn(scanResults); 346 347 mScanListenerCaptor.getValue().onResults(new WifiScanner.ScanData[]{scanData}); 348 mTestLooper.dispatchAll(); 349 350 // Updated with the correct SSID and ignored the different SSID. 351 assertThat(tracker.getWifiEntry().getLevel()).isEqualTo(BAD_LEVEL); 352 } 353 } 354