Home | History | Annotate | Download | only in shadows
      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 
     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 
     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    */
     79   public Map<Network, Boolean> getReportedNetworkConnectivity() {
     80     return new HashMap<>(reportedNetworkConnectivity);
     81   }
     82 
     83   @Implementation(minSdk = LOLLIPOP)
     84   protected void registerNetworkCallback(
     85       NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) {
     86     registerNetworkCallback(request, networkCallback, null);
     87   }
     88 
     89   @Implementation(minSdk = O)
     90   protected void registerNetworkCallback(
     91       NetworkRequest request,
     92       ConnectivityManager.NetworkCallback networkCallback,
     93       Handler handler) {
     94     networkCallbacks.add(networkCallback);
     95   }
     96 
     97   @Implementation(minSdk = LOLLIPOP)
     98   protected void requestNetwork(
     99       NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) {
    100     registerNetworkCallback(request, networkCallback);
    101   }
    102 
    103   @Implementation(minSdk = LOLLIPOP)
    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
    114   protected NetworkInfo getActiveNetworkInfo() {
    115     return activeNetworkInfo;
    116   }
    117 
    118   /**
    119    * @see #setActiveNetworkInfo(NetworkInfo)
    120    * @see #setNetworkInfo(int, NetworkInfo)
    121    */
    122   @Implementation(minSdk = M)
    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
    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
    146   protected NetworkInfo getNetworkInfo(int networkType) {
    147     return networkTypeToNetworkInfo.get(networkType);
    148   }
    149 
    150   @Implementation(minSdk = LOLLIPOP)
    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)
    160   protected Network[] getAllNetworks() {
    161     return netIdToNetwork.values().toArray(new Network[netIdToNetwork.size()]);
    162   }
    163 
    164   @Implementation
    165   protected boolean getBackgroundDataSetting() {
    166     return backgroundDataSetting;
    167   }
    168 
    169   @Implementation
    170   protected void setNetworkPreference(int preference) {
    171     networkPreference = preference;
    172   }
    173 
    174   @Implementation
    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
    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)
    197   protected boolean bindProcessToNetwork(Network network) {
    198     processBoundNetwork = network;
    199     return true;
    200   }
    201 
    202   @Implementation(minSdk = M)
    203   protected Network getBoundNetworkForProcess() {
    204     return processBoundNetwork;
    205   }
    206 
    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)
    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    */
    224   public void setCaptivePortalServerUrl(String captivePortalServerUrl) {
    225     this.captivePortalServerUrl = captivePortalServerUrl;
    226   }
    227 
    228   @HiddenApi @Implementation
    229   public void setBackgroundDataSetting(boolean b) {
    230     backgroundDataSetting = b;
    231   }
    232 
    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    */
    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    */
    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    */
    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    */
    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)
    316   protected boolean isDefaultNetworkActive() {
    317     return defaultNetworkActive;
    318   }
    319 
    320   @Implementation(minSdk = LOLLIPOP)
    321   protected void addDefaultNetworkActiveListener(final ConnectivityManager.OnNetworkActiveListener l) {
    322     onNetworkActiveListeners.add(l);
    323   }
    324 
    325   @Implementation(minSdk = LOLLIPOP)
    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)
    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)
    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    */
    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)
    369   protected void setAirplaneMode(boolean enable) {
    370     ShadowSettings.setAirplaneMode(enable);
    371   }
    372 
    373   /** @see #setLinkProperties(Network, LinkProperties) */
    374   @Implementation(minSdk = LOLLIPOP)
    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    */
    385   public void setLinkProperties(Network network, LinkProperties linkProperties) {
    386     linkPropertiesMap.put(network, linkProperties);
    387   }
    388 }
    389