• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
NetworkState(boolean connected, int type, int subtype)78     public NetworkState(boolean connected, int type, int subtype) {
79       this.connected = connected;
80       this.type = type;
81       this.subtype = subtype;
82     }
83 
isConnected()84     public boolean isConnected() {
85       return connected;
86     }
87 
getNetworkType()88     public int getNetworkType() {
89       return type;
90     }
91 
getNetworkSubType()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 
ConnectivityManagerDelegate(Context context)105     ConnectivityManagerDelegate(Context context) {
106       connectivityManager =
107           (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
108     }
109 
110     // For testing.
ConnectivityManagerDelegate()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      */
getNetworkState()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")
getNetworkState(Network network)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      */
getNetworkState(NetworkInfo networkInfo)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")
getAllNetworks()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")
getDefaultNetId()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")
hasInternetCapability(Network network)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 
WifiManagerDelegate(Context context)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.
WifiManagerDelegate()233     WifiManagerDelegate() {
234       // All the methods below should be overridden.
235       context = null;
236       wifiManager = null;
237       hasWifiPermission = false;
238     }
239 
getWifiSSID()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 
getHasWifiPermission()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      */
onConnectionTypeChanged(ConnectionType newConnectionType)282     public void onConnectionTypeChanged(ConnectionType newConnectionType);
283   }
284 
285   /**
286    * Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread.
287    */
NetworkMonitorAutoDetect(Observer observer, Context context)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    */
setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate)304   void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) {
305     connectivityManagerDelegate = delegate;
306   }
307 
308   /**
309    * Allows overriding the WifiManagerDelegate for tests.
310    */
setWifiManagerDelegateForTests(WifiManagerDelegate delegate)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    */
isReceiverRegisteredForTesting()319   boolean isReceiverRegisteredForTesting() {
320     return isRegistered;
321   }
322 
destroy()323   public void destroy() {
324     unregisterReceiver();
325   }
326 
327   /**
328    * Registers a BroadcastReceiver in the given context.
329    */
registerReceiver()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    */
unregisterReceiver()340   private void unregisterReceiver() {
341     if (isRegistered) {
342       isRegistered = false;
343       context.unregisterReceiver(this);
344     }
345   }
346 
getCurrentNetworkState()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    */
getDefaultNetId()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 
getCurrentConnectionType(NetworkState networkState)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 
getCurrentWifiSSID(NetworkState networkState)407   private String getCurrentWifiSSID(NetworkState networkState) {
408     if (getCurrentConnectionType(networkState) != ConnectionType.CONNECTION_WIFI) return "";
409     return wifiManagerDelegate.getWifiSSID();
410   }
411 
412   // BroadcastReceiver
413   @Override
onReceive(Context context, Intent intent)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 
connectionTypeChanged(NetworkState networkState)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")
networkToNetId(Network network)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