• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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