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
28package org.webrtc;
29
30import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
31
32import android.Manifest.permission;
33import android.annotation.SuppressLint;
34import android.content.BroadcastReceiver;
35import android.content.Context;
36import android.content.Intent;
37import android.content.IntentFilter;
38import android.content.pm.PackageManager;
39import android.net.ConnectivityManager;
40import android.net.Network;
41import android.net.NetworkCapabilities;
42import android.net.NetworkInfo;
43import android.net.wifi.WifiInfo;
44import android.net.wifi.WifiManager;
45import android.os.Build;
46import android.telephony.TelephonyManager;
47import 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 */
57public 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