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