1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.KITKAT; 4 import static android.os.Build.VERSION_CODES.LOLLIPOP; 5 import static android.os.Build.VERSION_CODES.M; 6 import static android.os.Build.VERSION_CODES.N; 7 import static android.os.Build.VERSION_CODES.O; 8 import static org.robolectric.RuntimeEnvironment.getApiLevel; 9 10 import android.net.ConnectivityManager; 11 import android.net.ConnectivityManager.OnNetworkActiveListener; 12 import android.net.LinkProperties; 13 import android.net.Network; 14 import android.net.NetworkCapabilities; 15 import android.net.NetworkInfo; 16 import android.net.NetworkRequest; 17 import android.os.Handler; 18 import java.util.HashMap; 19 import java.util.HashSet; 20 import java.util.Map; 21 import java.util.Set; 22 import org.robolectric.annotation.HiddenApi; 23 import org.robolectric.annotation.Implementation; 24 import org.robolectric.annotation.Implements; 25 import org.robolectric.shadow.api.Shadow; 26 27 @Implements(ConnectivityManager.class) 28 public class ShadowConnectivityManager { 29 30 // Package-private for tests. 31 static final int NET_ID_WIFI = ConnectivityManager.TYPE_WIFI; 32 static final int NET_ID_MOBILE = ConnectivityManager.TYPE_MOBILE; 33 34 private NetworkInfo activeNetworkInfo; 35 private boolean backgroundDataSetting; 36 private int networkPreference = ConnectivityManager.DEFAULT_NETWORK_PREFERENCE; 37 private final Map<Integer, NetworkInfo> networkTypeToNetworkInfo = new HashMap<>(); 38 39 private HashSet<ConnectivityManager.NetworkCallback> networkCallbacks = new HashSet<>(); 40 private final Map<Integer, Network> netIdToNetwork = new HashMap<>(); 41 private final Map<Integer, NetworkInfo> netIdToNetworkInfo = new HashMap<>(); 42 private Network processBoundNetwork; 43 private boolean defaultNetworkActive; 44 private HashSet<ConnectivityManager.OnNetworkActiveListener> onNetworkActiveListeners = 45 new HashSet<>(); 46 private Map<Network, Boolean> reportedNetworkConnectivity = new HashMap<>(); 47 private Map<Network, NetworkCapabilities> networkCapabilitiesMap = new HashMap<>(); 48 private String captivePortalServerUrl = "http://10.0.0.2"; 49 private final Map<Network, LinkProperties> linkPropertiesMap = new HashMap<>(); 50 ShadowConnectivityManager()51 public ShadowConnectivityManager() { 52 NetworkInfo wifi = ShadowNetworkInfo.newInstance(NetworkInfo.DetailedState.DISCONNECTED, 53 ConnectivityManager.TYPE_WIFI, 0, true, false); 54 networkTypeToNetworkInfo.put(ConnectivityManager.TYPE_WIFI, wifi); 55 56 NetworkInfo mobile = ShadowNetworkInfo.newInstance(NetworkInfo.DetailedState.CONNECTED, 57 ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_MOBILE_MMS, true, true); 58 networkTypeToNetworkInfo.put(ConnectivityManager.TYPE_MOBILE, mobile); 59 60 this.activeNetworkInfo = mobile; 61 62 if (getApiLevel() >= LOLLIPOP) { 63 netIdToNetwork.put(NET_ID_WIFI, ShadowNetwork.newInstance(NET_ID_WIFI)); 64 netIdToNetwork.put(NET_ID_MOBILE, ShadowNetwork.newInstance(NET_ID_MOBILE)); 65 netIdToNetworkInfo.put(NET_ID_WIFI, wifi); 66 netIdToNetworkInfo.put(NET_ID_MOBILE, mobile); 67 } 68 defaultNetworkActive = true; 69 } 70 getNetworkCallbacks()71 public Set<ConnectivityManager.NetworkCallback> getNetworkCallbacks() { 72 return networkCallbacks; 73 } 74 75 /** 76 * @return networks and their connectivity status which was reported with {@link 77 * #reportNetworkConnectivity}. 78 */ getReportedNetworkConnectivity()79 public Map<Network, Boolean> getReportedNetworkConnectivity() { 80 return new HashMap<>(reportedNetworkConnectivity); 81 } 82 83 @Implementation(minSdk = LOLLIPOP) registerNetworkCallback( NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback)84 protected void registerNetworkCallback( 85 NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) { 86 registerNetworkCallback(request, networkCallback, null); 87 } 88 89 @Implementation(minSdk = O) registerNetworkCallback( NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler)90 protected void registerNetworkCallback( 91 NetworkRequest request, 92 ConnectivityManager.NetworkCallback networkCallback, 93 Handler handler) { 94 networkCallbacks.add(networkCallback); 95 } 96 97 @Implementation(minSdk = LOLLIPOP) requestNetwork( NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback)98 protected void requestNetwork( 99 NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) { 100 registerNetworkCallback(request, networkCallback); 101 } 102 103 @Implementation(minSdk = LOLLIPOP) unregisterNetworkCallback(ConnectivityManager.NetworkCallback networkCallback)104 protected void unregisterNetworkCallback(ConnectivityManager.NetworkCallback networkCallback) { 105 if (networkCallback == null) { 106 throw new IllegalArgumentException("Invalid NetworkCallback"); 107 } 108 if (networkCallbacks.contains(networkCallback)) { 109 networkCallbacks.remove(networkCallback); 110 } 111 } 112 113 @Implementation getActiveNetworkInfo()114 protected NetworkInfo getActiveNetworkInfo() { 115 return activeNetworkInfo; 116 } 117 118 /** 119 * @see #setActiveNetworkInfo(NetworkInfo) 120 * @see #setNetworkInfo(int, NetworkInfo) 121 */ 122 @Implementation(minSdk = M) getActiveNetwork()123 protected Network getActiveNetwork() { 124 if (defaultNetworkActive) { 125 return netIdToNetwork.get(getActiveNetworkInfo().getType()); 126 } 127 return null; 128 } 129 130 /** 131 * @see #setActiveNetworkInfo(NetworkInfo) 132 * @see #setNetworkInfo(int, NetworkInfo) 133 */ 134 @Implementation getAllNetworkInfo()135 protected NetworkInfo[] getAllNetworkInfo() { 136 // todo(xian): is `defaultNetworkActive` really relevant here? 137 if (defaultNetworkActive) { 138 return networkTypeToNetworkInfo 139 .values() 140 .toArray(new NetworkInfo[networkTypeToNetworkInfo.size()]); 141 } 142 return null; 143 } 144 145 @Implementation getNetworkInfo(int networkType)146 protected NetworkInfo getNetworkInfo(int networkType) { 147 return networkTypeToNetworkInfo.get(networkType); 148 } 149 150 @Implementation(minSdk = LOLLIPOP) getNetworkInfo(Network network)151 protected NetworkInfo getNetworkInfo(Network network) { 152 if (network == null) { 153 return null; 154 } 155 ShadowNetwork shadowNetwork = Shadow.extract(network); 156 return netIdToNetworkInfo.get(shadowNetwork.getNetId()); 157 } 158 159 @Implementation(minSdk = LOLLIPOP) getAllNetworks()160 protected Network[] getAllNetworks() { 161 return netIdToNetwork.values().toArray(new Network[netIdToNetwork.size()]); 162 } 163 164 @Implementation getBackgroundDataSetting()165 protected boolean getBackgroundDataSetting() { 166 return backgroundDataSetting; 167 } 168 169 @Implementation setNetworkPreference(int preference)170 protected void setNetworkPreference(int preference) { 171 networkPreference = preference; 172 } 173 174 @Implementation getNetworkPreference()175 protected int getNetworkPreference() { 176 return networkPreference; 177 } 178 179 /** 180 * Counts {@link ConnectivityManager#TYPE_MOBILE} networks as metered. Other types will be 181 * considered unmetered. 182 * 183 * @return `true` if the active network is metered, otherwise `false`. 184 * @see #setActiveNetworkInfo(NetworkInfo) 185 * @see #setDefaultNetworkActive(boolean) 186 */ 187 @Implementation isActiveNetworkMetered()188 protected boolean isActiveNetworkMetered() { 189 if (defaultNetworkActive && activeNetworkInfo != null) { 190 return activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE; 191 } else { 192 return false; 193 } 194 } 195 196 @Implementation(minSdk = M) bindProcessToNetwork(Network network)197 protected boolean bindProcessToNetwork(Network network) { 198 processBoundNetwork = network; 199 return true; 200 } 201 202 @Implementation(minSdk = M) getBoundNetworkForProcess()203 protected Network getBoundNetworkForProcess() { 204 return processBoundNetwork; 205 } 206 setNetworkInfo(int networkType, NetworkInfo networkInfo)207 public void setNetworkInfo(int networkType, NetworkInfo networkInfo) { 208 networkTypeToNetworkInfo.put(networkType, networkInfo); 209 } 210 211 /** 212 * Returns the captive portal URL previously set with {@link #setCaptivePortalServerUrl}. 213 */ 214 @Implementation(minSdk = N) getCaptivePortalServerUrl()215 protected String getCaptivePortalServerUrl() { 216 return captivePortalServerUrl; 217 } 218 219 /** 220 * Sets the captive portal URL, which will be returned in {@link #getCaptivePortalServerUrl}. 221 * 222 * @param captivePortalServerUrl the url of captive portal. 223 */ setCaptivePortalServerUrl(String captivePortalServerUrl)224 public void setCaptivePortalServerUrl(String captivePortalServerUrl) { 225 this.captivePortalServerUrl = captivePortalServerUrl; 226 } 227 228 @HiddenApi @Implementation setBackgroundDataSetting(boolean b)229 public void setBackgroundDataSetting(boolean b) { 230 backgroundDataSetting = b; 231 } 232 setActiveNetworkInfo(NetworkInfo info)233 public void setActiveNetworkInfo(NetworkInfo info) { 234 if (getApiLevel() >= LOLLIPOP) { 235 activeNetworkInfo = info; 236 if (info != null) { 237 networkTypeToNetworkInfo.put(info.getType(), info); 238 netIdToNetwork.put(info.getType(), ShadowNetwork.newInstance(info.getType())); 239 netIdToNetworkInfo.put(info.getType(), info); 240 } else { 241 networkTypeToNetworkInfo.clear(); 242 netIdToNetwork.clear(); 243 } 244 } else { 245 activeNetworkInfo = info; 246 if (info != null) { 247 networkTypeToNetworkInfo.put(info.getType(), info); 248 } else { 249 networkTypeToNetworkInfo.clear(); 250 } 251 } 252 } 253 254 /** 255 * Adds new {@code network} to the list of all {@link android.net.Network}s. 256 * 257 * @param network The network. 258 * @param networkInfo The network info paired with the {@link android.net.Network}. 259 */ addNetwork(Network network, NetworkInfo networkInfo)260 public void addNetwork(Network network, NetworkInfo networkInfo) { 261 ShadowNetwork shadowNetwork = Shadow.extract(network); 262 int netId = shadowNetwork.getNetId(); 263 netIdToNetwork.put(netId, network); 264 netIdToNetworkInfo.put(netId, networkInfo); 265 } 266 267 /** 268 * Removes the {@code network} from the list of all {@link android.net.Network}s. 269 * @param network The network. 270 */ removeNetwork(Network network)271 public void removeNetwork(Network network) { 272 ShadowNetwork shadowNetwork = Shadow.extract(network); 273 int netId = shadowNetwork.getNetId(); 274 netIdToNetwork.remove(netId); 275 netIdToNetworkInfo.remove(netId); 276 } 277 278 /** 279 * Clears the list of all {@link android.net.Network}s. 280 */ clearAllNetworks()281 public void clearAllNetworks() { 282 netIdToNetwork.clear(); 283 netIdToNetworkInfo.clear(); 284 } 285 286 /** 287 * Sets the active state of the default network. 288 * 289 * By default this is true and affects the result of {@link 290 * ConnectivityManager#isActiveNetworkMetered()}, {@link 291 * ConnectivityManager#isDefaultNetworkActive()}, {@link ConnectivityManager#getActiveNetwork()} 292 * and {@link ConnectivityManager#getAllNetworkInfo()}. 293 * 294 * Calling this method with {@code true} after any listeners have been registered with {@link 295 * ConnectivityManager#addDefaultNetworkActiveListener(OnNetworkActiveListener)} will result in 296 * those listeners being fired. 297 * 298 * @param isActive The active state of the default network. 299 */ setDefaultNetworkActive(boolean isActive)300 public void setDefaultNetworkActive(boolean isActive) { 301 defaultNetworkActive = isActive; 302 if (defaultNetworkActive) { 303 for (ConnectivityManager.OnNetworkActiveListener l : onNetworkActiveListeners) { 304 if (l != null) { 305 l.onNetworkActive(); 306 } 307 } 308 } 309 } 310 311 /** 312 * @return `true` by default, or the value specifed via {@link #setDefaultNetworkActive(boolean)} 313 * @see #setDefaultNetworkActive(boolean) 314 */ 315 @Implementation(minSdk = LOLLIPOP) isDefaultNetworkActive()316 protected boolean isDefaultNetworkActive() { 317 return defaultNetworkActive; 318 } 319 320 @Implementation(minSdk = LOLLIPOP) addDefaultNetworkActiveListener(final ConnectivityManager.OnNetworkActiveListener l)321 protected void addDefaultNetworkActiveListener(final ConnectivityManager.OnNetworkActiveListener l) { 322 onNetworkActiveListeners.add(l); 323 } 324 325 @Implementation(minSdk = LOLLIPOP) removeDefaultNetworkActiveListener(ConnectivityManager.OnNetworkActiveListener l)326 protected void removeDefaultNetworkActiveListener(ConnectivityManager.OnNetworkActiveListener l) { 327 if (l == null) { 328 throw new IllegalArgumentException("Invalid OnNetworkActiveListener"); 329 } 330 if (onNetworkActiveListeners.contains(l)) { 331 onNetworkActiveListeners.remove(l); 332 } 333 } 334 335 @Implementation(minSdk = M) reportNetworkConnectivity(Network network, boolean hasConnectivity)336 protected void reportNetworkConnectivity(Network network, boolean hasConnectivity) { 337 reportedNetworkConnectivity.put(network, hasConnectivity); 338 } 339 340 /** 341 * Gets the network capabilities of a given {@link Network}. 342 * 343 * @param network The {@link Network} object identifying the network in question. 344 * @return The {@link android.net.NetworkCapabilities} for the network. 345 * @see #setNetworkCapabilities(Network, NetworkCapabilities) 346 */ 347 @Implementation(minSdk = LOLLIPOP) getNetworkCapabilities(Network network)348 protected NetworkCapabilities getNetworkCapabilities(Network network) { 349 return networkCapabilitiesMap.get(network); 350 } 351 352 /** 353 * Sets network capability and affects the result of {@link 354 * ConnectivityManager#getNetworkCapabilities(Network)} 355 * 356 * @param network The {@link Network} object identifying the network in question. 357 * @param networkCapabilities The {@link android.net.NetworkCapabilities} for the network. 358 */ setNetworkCapabilities(Network network, NetworkCapabilities networkCapabilities)359 public void setNetworkCapabilities(Network network, NetworkCapabilities networkCapabilities) { 360 networkCapabilitiesMap.put(network, networkCapabilities); 361 } 362 363 /** 364 * Sets the value for enabling/disabling airplane mode 365 * 366 * @param enable new status for airplane mode 367 */ 368 @Implementation(minSdk = KITKAT) setAirplaneMode(boolean enable)369 protected void setAirplaneMode(boolean enable) { 370 ShadowSettings.setAirplaneMode(enable); 371 } 372 373 /** @see #setLinkProperties(Network, LinkProperties) */ 374 @Implementation(minSdk = LOLLIPOP) getLinkProperties(Network network)375 protected LinkProperties getLinkProperties(Network network) { 376 return linkPropertiesMap.get(network); 377 } 378 379 /** 380 * Sets the LinkProperties for the given Network. 381 * 382 * <p>A LinkProperties can be constructed by 383 * `org.robolectric.util.ReflectionHelpers.callConstructor` in tests. 384 */ setLinkProperties(Network network, LinkProperties linkProperties)385 public void setLinkProperties(Network network, LinkProperties linkProperties) { 386 linkPropertiesMap.put(network, linkProperties); 387 } 388 } 389