Home | History | Annotate | Download | only in webrtc
      1 /*
      2  * libjingle
      3  * Copyright 2015 Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 package org.webrtc;
     29 
     30 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
     31 
     32 import android.Manifest.permission;
     33 import android.annotation.SuppressLint;
     34 import android.content.BroadcastReceiver;
     35 import android.content.Context;
     36 import android.content.Intent;
     37 import android.content.IntentFilter;
     38 import android.content.pm.PackageManager;
     39 import android.net.ConnectivityManager;
     40 import android.net.Network;
     41 import android.net.NetworkCapabilities;
     42 import android.net.NetworkInfo;
     43 import android.net.wifi.WifiInfo;
     44 import android.net.wifi.WifiManager;
     45 import android.os.Build;
     46 import android.telephony.TelephonyManager;
     47 import android.util.Log;
     48 
     49 /**
     50  * Borrowed from Chromium's
     51  * src/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
     52  *
     53  * Used by the NetworkMonitor to listen to platform changes in connectivity.
     54  * Note that use of this class requires that the app have the platform
     55  * ACCESS_NETWORK_STATE permission.
     56  */
     57 public class NetworkMonitorAutoDetect extends BroadcastReceiver {
     58   public static enum ConnectionType {
     59     CONNECTION_UNKNOWN,
     60     CONNECTION_ETHERNET,
     61     CONNECTION_WIFI,
     62     CONNECTION_4G,
     63     CONNECTION_3G,
     64     CONNECTION_2G,
     65     CONNECTION_BLUETOOTH,
     66     CONNECTION_NONE
     67   }
     68 
     69   static class NetworkState {
     70     private final boolean connected;
     71     // Defined from ConnectivityManager.TYPE_XXX for non-mobile; for mobile, it is
     72     // further divided into 2G, 3G, or 4G from the subtype.
     73     private final int type;
     74     // Defined from NetworkInfo.subtype, which is one of the TelephonyManager.NETWORK_TYPE_XXXs.
     75     // Will be useful to find the maximum bandwidth.
     76     private final int subtype;
     77 
     78     public NetworkState(boolean connected, int type, int subtype) {
     79       this.connected = connected;
     80       this.type = type;
     81       this.subtype = subtype;
     82     }
     83 
     84     public boolean isConnected() {
     85       return connected;
     86     }
     87 
     88     public int getNetworkType() {
     89       return type;
     90     }
     91 
     92     public int getNetworkSubType() {
     93       return subtype;
     94     }
     95   }
     96 
     97   /** Queries the ConnectivityManager for information about the current connection. */
     98   static class ConnectivityManagerDelegate {
     99     /**
    100      *  Note: In some rare Android systems connectivityManager is null.  We handle that
    101      *  gracefully below.
    102      */
    103     private final ConnectivityManager connectivityManager;
    104 
    105     ConnectivityManagerDelegate(Context context) {
    106       connectivityManager =
    107           (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    108     }
    109 
    110     // For testing.
    111     ConnectivityManagerDelegate() {
    112       // All the methods below should be overridden.
    113       connectivityManager = null;
    114     }
    115 
    116     /**
    117      * Returns connection type and status information about the current
    118      * default network.
    119      */
    120     NetworkState getNetworkState() {
    121       if (connectivityManager == null) {
    122         return new NetworkState(false, -1, -1);
    123       }
    124       return getNetworkState(connectivityManager.getActiveNetworkInfo());
    125     }
    126 
    127     /**
    128      * Returns connection type and status information about |network|.
    129      * Only callable on Lollipop and newer releases.
    130      */
    131     @SuppressLint("NewApi")
    132     NetworkState getNetworkState(Network network) {
    133       if (connectivityManager == null) {
    134         return new NetworkState(false, -1, -1);
    135       }
    136       return getNetworkState(connectivityManager.getNetworkInfo(network));
    137     }
    138 
    139     /**
    140      * Returns connection type and status information gleaned from networkInfo.
    141      */
    142     NetworkState getNetworkState(NetworkInfo networkInfo) {
    143       if (networkInfo == null || !networkInfo.isConnected()) {
    144         return new NetworkState(false, -1, -1);
    145       }
    146       return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype());
    147     }
    148 
    149     /**
    150      * Returns all connected networks.
    151      * Only callable on Lollipop and newer releases.
    152      */
    153     @SuppressLint("NewApi")
    154     Network[] getAllNetworks() {
    155       if (connectivityManager == null) {
    156         return new Network[0];
    157       }
    158       return connectivityManager.getAllNetworks();
    159     }
    160 
    161     /**
    162      * Returns the NetID of the current default network. Returns
    163      * INVALID_NET_ID if no current default network connected.
    164      * Only callable on Lollipop and newer releases.
    165      */
    166     @SuppressLint("NewApi")
    167     int getDefaultNetId() {
    168       if (connectivityManager == null) {
    169         return INVALID_NET_ID;
    170       }
    171       // Android Lollipop had no API to get the default network; only an
    172       // API to return the NetworkInfo for the default network. To
    173       // determine the default network one can find the network with
    174       // type matching that of the default network.
    175       final NetworkInfo defaultNetworkInfo = connectivityManager.getActiveNetworkInfo();
    176       if (defaultNetworkInfo == null) {
    177         return INVALID_NET_ID;
    178       }
    179       final Network[] networks = getAllNetworks();
    180       int defaultNetId = INVALID_NET_ID;
    181       for (Network network : networks) {
    182         if (!hasInternetCapability(network)) {
    183           continue;
    184         }
    185         final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
    186         if (networkInfo != null && networkInfo.getType() == defaultNetworkInfo.getType()) {
    187           // There should not be multiple connected networks of the
    188           // same type. At least as of Android Marshmallow this is
    189           // not supported. If this becomes supported this assertion
    190           // may trigger. At that point we could consider using
    191           // ConnectivityManager.getDefaultNetwork() though this
    192           // may give confusing results with VPNs and is only
    193           // available with Android Marshmallow.
    194           assert defaultNetId == INVALID_NET_ID;
    195           defaultNetId = networkToNetId(network);
    196         }
    197       }
    198       return defaultNetId;
    199     }
    200 
    201     /**
    202      * Returns true if {@code network} can provide Internet access. Can be used to
    203      * ignore specialized networks (e.g. IMS, FOTA).
    204      */
    205     @SuppressLint("NewApi")
    206     boolean hasInternetCapability(Network network) {
    207       if (connectivityManager == null) {
    208         return false;
    209       }
    210       final NetworkCapabilities capabilities =
    211           connectivityManager.getNetworkCapabilities(network);
    212       return capabilities != null && capabilities.hasCapability(NET_CAPABILITY_INTERNET);
    213     }
    214   }
    215 
    216   /** Queries the WifiManager for SSID of the current Wifi connection. */
    217   static class WifiManagerDelegate {
    218     private final Context context;
    219     private final WifiManager wifiManager;
    220     private final boolean hasWifiPermission;
    221 
    222     WifiManagerDelegate(Context context) {
    223       this.context = context;
    224 
    225       hasWifiPermission = context.getPackageManager().checkPermission(
    226           permission.ACCESS_WIFI_STATE, context.getPackageName())
    227           == PackageManager.PERMISSION_GRANTED;
    228       wifiManager = hasWifiPermission
    229           ? (WifiManager) context.getSystemService(Context.WIFI_SERVICE) : null;
    230     }
    231 
    232     // For testing.
    233     WifiManagerDelegate() {
    234       // All the methods below should be overridden.
    235       context = null;
    236       wifiManager = null;
    237       hasWifiPermission = false;
    238     }
    239 
    240     String getWifiSSID() {
    241       final Intent intent = context.registerReceiver(null,
    242           new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION));
    243       if (intent != null) {
    244         final WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
    245         if (wifiInfo != null) {
    246           final String ssid = wifiInfo.getSSID();
    247           if (ssid != null) {
    248             return ssid;
    249           }
    250         }
    251       }
    252       return "";
    253     }
    254 
    255     boolean getHasWifiPermission() {
    256       return hasWifiPermission;
    257     }
    258   }
    259 
    260   static final int INVALID_NET_ID = -1;
    261   private static final String TAG = "NetworkMonitorAutoDetect";
    262   private final IntentFilter intentFilter;
    263 
    264   // Observer for the connection type change.
    265   private final Observer observer;
    266 
    267   private final Context context;
    268   // connectivityManagerDelegates and wifiManagerDelegate are only non-final for testing.
    269   private ConnectivityManagerDelegate connectivityManagerDelegate;
    270   private WifiManagerDelegate wifiManagerDelegate;
    271   private boolean isRegistered;
    272   private ConnectionType connectionType;
    273   private String wifiSSID;
    274 
    275   /**
    276    * Observer interface by which observer is notified of network changes.
    277    */
    278   public static interface Observer {
    279     /**
    280      * Called when default network changes.
    281      */
    282     public void onConnectionTypeChanged(ConnectionType newConnectionType);
    283   }
    284 
    285   /**
    286    * Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread.
    287    */
    288   public NetworkMonitorAutoDetect(Observer observer, Context context) {
    289     this.observer = observer;
    290     this.context = context;
    291     connectivityManagerDelegate = new ConnectivityManagerDelegate(context);
    292     wifiManagerDelegate = new WifiManagerDelegate(context);
    293 
    294     final NetworkState networkState = connectivityManagerDelegate.getNetworkState();
    295     connectionType = getCurrentConnectionType(networkState);
    296     wifiSSID = getCurrentWifiSSID(networkState);
    297     intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    298     registerReceiver();
    299   }
    300 
    301   /**
    302    * Allows overriding the ConnectivityManagerDelegate for tests.
    303    */
    304   void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) {
    305     connectivityManagerDelegate = delegate;
    306   }
    307 
    308   /**
    309    * Allows overriding the WifiManagerDelegate for tests.
    310    */
    311   void setWifiManagerDelegateForTests(WifiManagerDelegate delegate) {
    312     wifiManagerDelegate = delegate;
    313   }
    314 
    315   /**
    316    * Returns whether the object has registered to receive network connectivity intents.
    317    * Visible for testing.
    318    */
    319   boolean isReceiverRegisteredForTesting() {
    320     return isRegistered;
    321   }
    322 
    323   public void destroy() {
    324     unregisterReceiver();
    325   }
    326 
    327   /**
    328    * Registers a BroadcastReceiver in the given context.
    329    */
    330   private void registerReceiver() {
    331     if (!isRegistered) {
    332       isRegistered = true;
    333       context.registerReceiver(this, intentFilter);
    334     }
    335   }
    336 
    337   /**
    338    * Unregisters the BroadcastReceiver in the given context.
    339    */
    340   private void unregisterReceiver() {
    341     if (isRegistered) {
    342       isRegistered = false;
    343       context.unregisterReceiver(this);
    344     }
    345   }
    346 
    347   public NetworkState getCurrentNetworkState() {
    348     return connectivityManagerDelegate.getNetworkState();
    349   }
    350 
    351   /**
    352    * Returns NetID of device's current default connected network used for
    353    * communication.
    354    * Only implemented on Lollipop and newer releases, returns INVALID_NET_ID
    355    * when not implemented.
    356    */
    357   public int getDefaultNetId() {
    358     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
    359       return INVALID_NET_ID;
    360     }
    361     return connectivityManagerDelegate.getDefaultNetId();
    362   }
    363 
    364   public ConnectionType getCurrentConnectionType(NetworkState networkState) {
    365     if (!networkState.isConnected()) {
    366       return ConnectionType.CONNECTION_NONE;
    367     }
    368 
    369     switch (networkState.getNetworkType()) {
    370       case ConnectivityManager.TYPE_ETHERNET:
    371         return ConnectionType.CONNECTION_ETHERNET;
    372       case ConnectivityManager.TYPE_WIFI:
    373         return ConnectionType.CONNECTION_WIFI;
    374       case ConnectivityManager.TYPE_WIMAX:
    375         return ConnectionType.CONNECTION_4G;
    376       case ConnectivityManager.TYPE_BLUETOOTH:
    377         return ConnectionType.CONNECTION_BLUETOOTH;
    378       case ConnectivityManager.TYPE_MOBILE:
    379         // Use information from TelephonyManager to classify the connection.
    380         switch (networkState.getNetworkSubType()) {
    381           case TelephonyManager.NETWORK_TYPE_GPRS:
    382           case TelephonyManager.NETWORK_TYPE_EDGE:
    383           case TelephonyManager.NETWORK_TYPE_CDMA:
    384           case TelephonyManager.NETWORK_TYPE_1xRTT:
    385           case TelephonyManager.NETWORK_TYPE_IDEN:
    386             return ConnectionType.CONNECTION_2G;
    387           case TelephonyManager.NETWORK_TYPE_UMTS:
    388           case TelephonyManager.NETWORK_TYPE_EVDO_0:
    389           case TelephonyManager.NETWORK_TYPE_EVDO_A:
    390           case TelephonyManager.NETWORK_TYPE_HSDPA:
    391           case TelephonyManager.NETWORK_TYPE_HSUPA:
    392           case TelephonyManager.NETWORK_TYPE_HSPA:
    393           case TelephonyManager.NETWORK_TYPE_EVDO_B:
    394           case TelephonyManager.NETWORK_TYPE_EHRPD:
    395           case TelephonyManager.NETWORK_TYPE_HSPAP:
    396             return ConnectionType.CONNECTION_3G;
    397           case TelephonyManager.NETWORK_TYPE_LTE:
    398             return ConnectionType.CONNECTION_4G;
    399           default:
    400             return ConnectionType.CONNECTION_UNKNOWN;
    401         }
    402       default:
    403         return ConnectionType.CONNECTION_UNKNOWN;
    404     }
    405   }
    406 
    407   private String getCurrentWifiSSID(NetworkState networkState) {
    408     if (getCurrentConnectionType(networkState) != ConnectionType.CONNECTION_WIFI) return "";
    409     return wifiManagerDelegate.getWifiSSID();
    410   }
    411 
    412   // BroadcastReceiver
    413   @Override
    414   public void onReceive(Context context, Intent intent) {
    415     final NetworkState networkState = getCurrentNetworkState();
    416     if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
    417       connectionTypeChanged(networkState);
    418     }
    419   }
    420 
    421   private void connectionTypeChanged(NetworkState networkState) {
    422     ConnectionType newConnectionType = getCurrentConnectionType(networkState);
    423     String newWifiSSID = getCurrentWifiSSID(networkState);
    424     if (newConnectionType == connectionType && newWifiSSID.equals(wifiSSID)) return;
    425 
    426     connectionType = newConnectionType;
    427     wifiSSID = newWifiSSID;
    428     Log.d(TAG, "Network connectivity changed, type is: " + connectionType);
    429     observer.onConnectionTypeChanged(newConnectionType);
    430   }
    431 
    432   /**
    433    * Extracts NetID of network. Only available on Lollipop and newer releases.
    434    */
    435   @SuppressLint("NewApi")
    436   private static int networkToNetId(Network network) {
    437     // NOTE(pauljensen): This depends on Android framework implementation details.
    438     // Fortunately this functionality is unlikely to ever change.
    439     // TODO(honghaiz): When we update to Android M SDK, use Network.getNetworkHandle().
    440     return Integer.parseInt(network.toString());
    441   }
    442 }
    443