1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 4 import static android.os.Build.VERSION_CODES.KITKAT; 5 import static android.os.Build.VERSION_CODES.LOLLIPOP; 6 7 import android.content.Context; 8 import android.net.ConnectivityManager; 9 import android.net.DhcpInfo; 10 import android.net.NetworkInfo; 11 import android.net.wifi.ScanResult; 12 import android.net.wifi.WifiConfiguration; 13 import android.net.wifi.WifiInfo; 14 import android.net.wifi.WifiManager; 15 import android.net.wifi.WifiManager.MulticastLock; 16 import android.util.Pair; 17 import java.util.ArrayList; 18 import java.util.LinkedHashMap; 19 import java.util.List; 20 import java.util.Map; 21 import java.util.concurrent.atomic.AtomicInteger; 22 import org.robolectric.RuntimeEnvironment; 23 import org.robolectric.annotation.HiddenApi; 24 import org.robolectric.annotation.Implementation; 25 import org.robolectric.annotation.Implements; 26 import org.robolectric.annotation.RealObject; 27 import org.robolectric.shadow.api.Shadow; 28 import org.robolectric.util.ReflectionHelpers; 29 30 /** 31 * Shadow for {@link android.net.wifi.WifiManager}. 32 */ 33 @Implements(WifiManager.class) 34 public class ShadowWifiManager { 35 private static final int LOCAL_HOST = 2130706433; 36 37 private static float sSignalLevelInPercent = 1f; 38 private boolean accessWifiStatePermission = true; 39 private boolean wifiEnabled = true; 40 private boolean wasSaved = false; 41 private WifiInfo wifiInfo; 42 private List<ScanResult> scanResults; 43 private final Map<Integer, WifiConfiguration> networkIdToConfiguredNetworks = new LinkedHashMap<>(); 44 private Pair<Integer, Boolean> lastEnabledNetwork; 45 private DhcpInfo dhcpInfo; 46 private boolean isScanAlwaysAvailable = true; 47 private boolean startScanSucceeds = true; 48 private boolean is5GHzBandSupported = false; 49 private AtomicInteger activeLockCount = new AtomicInteger(0); 50 @RealObject WifiManager wifiManager; 51 52 @Implementation setWifiEnabled(boolean wifiEnabled)53 protected boolean setWifiEnabled(boolean wifiEnabled) { 54 checkAccessWifiStatePermission(); 55 this.wifiEnabled = wifiEnabled; 56 return true; 57 } 58 59 @Implementation isWifiEnabled()60 protected boolean isWifiEnabled() { 61 checkAccessWifiStatePermission(); 62 return wifiEnabled; 63 } 64 65 @Implementation getWifiState()66 protected int getWifiState() { 67 if (isWifiEnabled()) { 68 return WifiManager.WIFI_STATE_ENABLED; 69 } else { 70 return WifiManager.WIFI_STATE_DISABLED; 71 } 72 } 73 74 @Implementation getConnectionInfo()75 protected WifiInfo getConnectionInfo() { 76 checkAccessWifiStatePermission(); 77 if (wifiInfo == null) { 78 wifiInfo = ReflectionHelpers.callConstructor(WifiInfo.class); 79 } 80 return wifiInfo; 81 } 82 83 @Implementation(minSdk = LOLLIPOP) is5GHzBandSupported()84 protected boolean is5GHzBandSupported() { 85 return is5GHzBandSupported; 86 } 87 88 /** Sets whether 5ghz band is supported. */ setIs5GHzBandSupported(boolean is5GHzBandSupported)89 public void setIs5GHzBandSupported(boolean is5GHzBandSupported) { 90 this.is5GHzBandSupported = is5GHzBandSupported; 91 } 92 93 /** 94 * Sets the connection info as the provided {@link WifiInfo}. 95 */ setConnectionInfo(WifiInfo wifiInfo)96 public void setConnectionInfo(WifiInfo wifiInfo) { 97 this.wifiInfo = wifiInfo; 98 } 99 100 /** Sets the return value of {@link #startScan}. */ setStartScanSucceeds(boolean succeeds)101 public void setStartScanSucceeds(boolean succeeds) { 102 this.startScanSucceeds = succeeds; 103 } 104 105 @Implementation getScanResults()106 protected List<ScanResult> getScanResults() { 107 return scanResults; 108 } 109 110 @Implementation getConfiguredNetworks()111 protected List<WifiConfiguration> getConfiguredNetworks() { 112 final ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>(); 113 for (WifiConfiguration wifiConfiguration : networkIdToConfiguredNetworks.values()) { 114 wifiConfigurations.add(wifiConfiguration); 115 } 116 return wifiConfigurations; 117 } 118 119 @Implementation(minSdk = LOLLIPOP) getPrivilegedConfiguredNetworks()120 protected List<WifiConfiguration> getPrivilegedConfiguredNetworks() { 121 return getConfiguredNetworks(); 122 } 123 124 @Implementation addNetwork(WifiConfiguration config)125 protected int addNetwork(WifiConfiguration config) { 126 int networkId = networkIdToConfiguredNetworks.size(); 127 config.networkId = -1; 128 networkIdToConfiguredNetworks.put(networkId, makeCopy(config, networkId)); 129 return networkId; 130 } 131 132 @Implementation removeNetwork(int netId)133 protected boolean removeNetwork(int netId) { 134 networkIdToConfiguredNetworks.remove(netId); 135 return true; 136 } 137 138 @Implementation updateNetwork(WifiConfiguration config)139 protected int updateNetwork(WifiConfiguration config) { 140 if (config == null || config.networkId < 0) { 141 return -1; 142 } 143 networkIdToConfiguredNetworks.put(config.networkId, makeCopy(config, config.networkId)); 144 return config.networkId; 145 } 146 147 @Implementation saveConfiguration()148 protected boolean saveConfiguration() { 149 wasSaved = true; 150 return true; 151 } 152 153 @Implementation enableNetwork(int netId, boolean disableOthers)154 protected boolean enableNetwork(int netId, boolean disableOthers) { 155 lastEnabledNetwork = new Pair<>(netId, disableOthers); 156 return true; 157 } 158 159 @Implementation createWifiLock(int lockType, String tag)160 protected WifiManager.WifiLock createWifiLock(int lockType, String tag) { 161 WifiManager.WifiLock wifiLock = ReflectionHelpers.callConstructor(WifiManager.WifiLock.class); 162 shadowOf(wifiLock).setWifiManager(wifiManager); 163 return wifiLock; 164 } 165 166 @Implementation createWifiLock(String tag)167 protected WifiManager.WifiLock createWifiLock(String tag) { 168 return createWifiLock(WifiManager.WIFI_MODE_FULL, tag); 169 } 170 171 @Implementation createMulticastLock(String tag)172 protected MulticastLock createMulticastLock(String tag) { 173 MulticastLock multicastLock = ReflectionHelpers.callConstructor(MulticastLock.class); 174 shadowOf(multicastLock).setWifiManager(wifiManager); 175 return multicastLock; 176 } 177 178 @Implementation calculateSignalLevel(int rssi, int numLevels)179 protected static int calculateSignalLevel(int rssi, int numLevels) { 180 return (int) (sSignalLevelInPercent * (numLevels - 1)); 181 } 182 183 /** 184 * Does nothing and returns the configured success status. 185 * 186 * <p>That is different from the Android implementation which always returns {@code true} up to 187 * and including Android 8, and either {@code true} or {@code false} on Android 9+. 188 * 189 * @return the value configured by {@link #setStartScanSucceeds}, or {@code true} if that method 190 * was never called. 191 */ 192 @Implementation startScan()193 protected boolean startScan() { 194 return startScanSucceeds; 195 } 196 197 @Implementation getDhcpInfo()198 protected DhcpInfo getDhcpInfo() { 199 return dhcpInfo; 200 } 201 202 @Implementation(minSdk = JELLY_BEAN_MR2) isScanAlwaysAvailable()203 protected boolean isScanAlwaysAvailable() { 204 return isScanAlwaysAvailable; 205 } 206 207 @HiddenApi 208 @Implementation(minSdk = KITKAT) connect(WifiConfiguration wifiConfiguration, WifiManager.ActionListener listener)209 protected void connect(WifiConfiguration wifiConfiguration, WifiManager.ActionListener listener) { 210 WifiInfo wifiInfo = getConnectionInfo(); 211 212 String ssid = isQuoted(wifiConfiguration.SSID) 213 ? stripQuotes(wifiConfiguration.SSID) 214 : wifiConfiguration.SSID; 215 216 ShadowWifiInfo shadowWifiInfo = Shadow.extract(wifiInfo); 217 shadowWifiInfo.setSSID(ssid); 218 shadowWifiInfo.setBSSID(wifiConfiguration.BSSID); 219 shadowWifiInfo.setNetworkId(wifiConfiguration.networkId); 220 setConnectionInfo(wifiInfo); 221 222 // Now that we're "connected" to wifi, update Dhcp and point it to localhost. 223 DhcpInfo dhcpInfo = new DhcpInfo(); 224 dhcpInfo.gateway = LOCAL_HOST; 225 dhcpInfo.ipAddress = LOCAL_HOST; 226 setDhcpInfo(dhcpInfo); 227 228 // Now add the network to ConnectivityManager. 229 NetworkInfo networkInfo = 230 ShadowNetworkInfo.newInstance( 231 NetworkInfo.DetailedState.CONNECTED, 232 ConnectivityManager.TYPE_WIFI, 233 0 /* subType */, 234 true /* isAvailable */, 235 true /* isConnected */); 236 ShadowConnectivityManager connectivityManager = 237 Shadow.extract( 238 RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE)); 239 connectivityManager.setActiveNetworkInfo(networkInfo); 240 241 if (listener != null) { 242 listener.onSuccess(); 243 } 244 } 245 246 @HiddenApi 247 @Implementation(minSdk = KITKAT) connect(int networkId, WifiManager.ActionListener listener)248 protected void connect(int networkId, WifiManager.ActionListener listener) { 249 WifiConfiguration wifiConfiguration = new WifiConfiguration(); 250 wifiConfiguration.networkId = networkId; 251 wifiConfiguration.SSID = ""; 252 wifiConfiguration.BSSID = ""; 253 connect(wifiConfiguration, listener); 254 } 255 isQuoted(String str)256 private static boolean isQuoted(String str) { 257 if (str == null || str.length() < 2) { 258 return false; 259 } 260 261 return str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"'; 262 } 263 stripQuotes(String str)264 private static String stripQuotes(String str) { 265 return str.substring(1, str.length() - 1); 266 } 267 268 @Implementation reconnect()269 protected boolean reconnect() { 270 WifiConfiguration wifiConfiguration = getMostRecentNetwork(); 271 if (wifiConfiguration == null) { 272 return false; 273 } 274 275 connect(wifiConfiguration, null); 276 return true; 277 } 278 getMostRecentNetwork()279 private WifiConfiguration getMostRecentNetwork() { 280 if (getLastEnabledNetwork() == null) { 281 return null; 282 } 283 284 return getWifiConfiguration(getLastEnabledNetwork().first); 285 } 286 setSignalLevelInPercent(float level)287 public static void setSignalLevelInPercent(float level) { 288 if (level < 0 || level > 1) { 289 throw new IllegalArgumentException("level needs to be between 0 and 1"); 290 } 291 sSignalLevelInPercent = level; 292 } 293 setAccessWifiStatePermission(boolean accessWifiStatePermission)294 public void setAccessWifiStatePermission(boolean accessWifiStatePermission) { 295 this.accessWifiStatePermission = accessWifiStatePermission; 296 } 297 setScanResults(List<ScanResult> scanResults)298 public void setScanResults(List<ScanResult> scanResults) { 299 this.scanResults = scanResults; 300 } 301 setDhcpInfo(DhcpInfo dhcpInfo)302 public void setDhcpInfo(DhcpInfo dhcpInfo) { 303 this.dhcpInfo = dhcpInfo; 304 } 305 getLastEnabledNetwork()306 public Pair<Integer, Boolean> getLastEnabledNetwork() { 307 return lastEnabledNetwork; 308 } 309 310 /** Returns the number of WifiLocks and MulticastLocks that are currently acquired. */ getActiveLockCount()311 public int getActiveLockCount() { 312 return activeLockCount.get(); 313 } 314 wasConfigurationSaved()315 public boolean wasConfigurationSaved() { 316 return wasSaved; 317 } 318 setIsScanAlwaysAvailable(boolean isScanAlwaysAvailable)319 public void setIsScanAlwaysAvailable(boolean isScanAlwaysAvailable) { 320 this.isScanAlwaysAvailable = isScanAlwaysAvailable; 321 } 322 checkAccessWifiStatePermission()323 private void checkAccessWifiStatePermission() { 324 if (!accessWifiStatePermission) { 325 throw new SecurityException(); 326 } 327 } 328 makeCopy(WifiConfiguration config, int networkId)329 private WifiConfiguration makeCopy(WifiConfiguration config, int networkId) { 330 ShadowWifiConfiguration shadowWifiConfiguration = Shadow.extract(config); 331 WifiConfiguration copy = shadowWifiConfiguration.copy(); 332 copy.networkId = networkId; 333 return copy; 334 } 335 getWifiConfiguration(int netId)336 public WifiConfiguration getWifiConfiguration(int netId) { 337 return networkIdToConfiguredNetworks.get(netId); 338 } 339 340 @Implements(WifiManager.WifiLock.class) 341 public static class ShadowWifiLock { 342 private int refCount; 343 private boolean refCounted = true; 344 private boolean locked; 345 private WifiManager wifiManager; 346 public static final int MAX_ACTIVE_LOCKS = 50; 347 setWifiManager(WifiManager wifiManager)348 private void setWifiManager(WifiManager wifiManager) { 349 this.wifiManager = wifiManager; 350 } 351 352 @Implementation acquire()353 protected synchronized void acquire() { 354 if (wifiManager != null) { 355 shadowOf(wifiManager).activeLockCount.getAndIncrement(); 356 } 357 if (refCounted) { 358 if (++refCount >= MAX_ACTIVE_LOCKS) throw new UnsupportedOperationException("Exceeded maximum number of wifi locks"); 359 } else { 360 locked = true; 361 } 362 } 363 364 @Implementation release()365 protected synchronized void release() { 366 if (wifiManager != null) { 367 shadowOf(wifiManager).activeLockCount.getAndDecrement(); 368 } 369 if (refCounted) { 370 if (--refCount < 0) throw new RuntimeException("WifiLock under-locked"); 371 } else { 372 locked = false; 373 } 374 } 375 376 @Implementation isHeld()377 protected synchronized boolean isHeld() { 378 return refCounted ? refCount > 0 : locked; 379 } 380 381 @Implementation setReferenceCounted(boolean refCounted)382 protected void setReferenceCounted(boolean refCounted) { 383 this.refCounted = refCounted; 384 } 385 } 386 387 @Implements(MulticastLock.class) 388 public static class ShadowMulticastLock { 389 private int refCount; 390 private boolean refCounted = true; 391 private boolean locked; 392 static final int MAX_ACTIVE_LOCKS = 50; 393 private WifiManager wifiManager; 394 setWifiManager(WifiManager wifiManager)395 private void setWifiManager(WifiManager wifiManager) { 396 this.wifiManager = wifiManager; 397 } 398 399 @Implementation acquire()400 protected void acquire() { 401 if (wifiManager != null) { 402 shadowOf(wifiManager).activeLockCount.getAndIncrement(); 403 } 404 if (refCounted) { 405 if (++refCount >= MAX_ACTIVE_LOCKS) throw new UnsupportedOperationException("Exceeded maximum number of wifi locks"); 406 } else { 407 locked = true; 408 } 409 } 410 411 @Implementation release()412 protected synchronized void release() { 413 if (wifiManager != null) { 414 shadowOf(wifiManager).activeLockCount.getAndDecrement(); 415 } 416 if (refCounted) { 417 if (--refCount < 0) throw new RuntimeException("WifiLock under-locked"); 418 } else { 419 locked = false; 420 } 421 } 422 423 @Implementation setReferenceCounted(boolean refCounted)424 protected void setReferenceCounted(boolean refCounted) { 425 this.refCounted = refCounted; 426 } 427 428 @Implementation isHeld()429 protected synchronized boolean isHeld() { 430 return refCounted ? refCount > 0 : locked; 431 } 432 } 433 shadowOf(WifiManager.WifiLock o)434 private static ShadowWifiLock shadowOf(WifiManager.WifiLock o) { 435 return Shadow.extract(o); 436 } 437 shadowOf(WifiManager.MulticastLock o)438 private static ShadowMulticastLock shadowOf(WifiManager.MulticastLock o) { 439 return Shadow.extract(o); 440 } 441 shadowOf(WifiManager o)442 private static ShadowWifiManager shadowOf(WifiManager o) { 443 return Shadow.extract(o); 444 } 445 } 446