| /* |
| * libjingle |
| * Copyright 2015 Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.webrtc; |
| |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; |
| |
| import android.Manifest.permission; |
| import android.annotation.SuppressLint; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.net.ConnectivityManager; |
| import android.net.Network; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkInfo; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.WifiManager; |
| import android.os.Build; |
| import android.telephony.TelephonyManager; |
| import android.util.Log; |
| |
| /** |
| * Borrowed from Chromium's |
| * src/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java |
| * |
| * Used by the NetworkMonitor to listen to platform changes in connectivity. |
| * Note that use of this class requires that the app have the platform |
| * ACCESS_NETWORK_STATE permission. |
| */ |
| public class NetworkMonitorAutoDetect extends BroadcastReceiver { |
| static enum ConnectionType { |
| CONNECTION_UNKNOWN, |
| CONNECTION_ETHERNET, |
| CONNECTION_WIFI, |
| CONNECTION_4G, |
| CONNECTION_3G, |
| CONNECTION_2G, |
| CONNECTION_BLUETOOTH, |
| CONNECTION_NONE |
| } |
| |
| static class NetworkState { |
| private final boolean connected; |
| // Defined from ConnectivityManager.TYPE_XXX for non-mobile; for mobile, it is |
| // further divided into 2G, 3G, or 4G from the subtype. |
| private final int type; |
| // Defined from NetworkInfo.subtype, which is one of the TelephonyManager.NETWORK_TYPE_XXXs. |
| // Will be useful to find the maximum bandwidth. |
| private final int subtype; |
| |
| public NetworkState(boolean connected, int type, int subtype) { |
| this.connected = connected; |
| this.type = type; |
| this.subtype = subtype; |
| } |
| |
| public boolean isConnected() { |
| return connected; |
| } |
| |
| public int getNetworkType() { |
| return type; |
| } |
| |
| public int getNetworkSubType() { |
| return subtype; |
| } |
| } |
| |
| /** Queries the ConnectivityManager for information about the current connection. */ |
| static class ConnectivityManagerDelegate { |
| private final ConnectivityManager connectivityManager; |
| |
| ConnectivityManagerDelegate(Context context) { |
| connectivityManager = |
| (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); |
| } |
| |
| // For testing. |
| ConnectivityManagerDelegate() { |
| // All the methods below should be overridden. |
| connectivityManager = null; |
| } |
| |
| /** |
| * Returns connection type and status information about the current |
| * default network. |
| */ |
| NetworkState getNetworkState() { |
| return getNetworkState(connectivityManager.getActiveNetworkInfo()); |
| } |
| |
| /** |
| * Returns connection type and status information about |network|. |
| * Only callable on Lollipop and newer releases. |
| */ |
| @SuppressLint("NewApi") |
| NetworkState getNetworkState(Network network) { |
| return getNetworkState(connectivityManager.getNetworkInfo(network)); |
| } |
| |
| /** |
| * Returns connection type and status information gleaned from networkInfo. |
| */ |
| NetworkState getNetworkState(NetworkInfo networkInfo) { |
| if (networkInfo == null || !networkInfo.isConnected()) { |
| return new NetworkState(false, -1, -1); |
| } |
| return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype()); |
| } |
| |
| /** |
| * Returns all connected networks. |
| * Only callable on Lollipop and newer releases. |
| */ |
| @SuppressLint("NewApi") |
| Network[] getAllNetworks() { |
| return connectivityManager.getAllNetworks(); |
| } |
| |
| /** |
| * Returns the NetID of the current default network. Returns |
| * INVALID_NET_ID if no current default network connected. |
| * Only callable on Lollipop and newer releases. |
| */ |
| @SuppressLint("NewApi") |
| int getDefaultNetId() { |
| // Android Lollipop had no API to get the default network; only an |
| // API to return the NetworkInfo for the default network. To |
| // determine the default network one can find the network with |
| // type matching that of the default network. |
| final NetworkInfo defaultNetworkInfo = connectivityManager.getActiveNetworkInfo(); |
| if (defaultNetworkInfo == null) { |
| return INVALID_NET_ID; |
| } |
| final Network[] networks = getAllNetworks(); |
| int defaultNetId = INVALID_NET_ID; |
| for (Network network : networks) { |
| if (!hasInternetCapability(network)) { |
| continue; |
| } |
| final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); |
| if (networkInfo != null && networkInfo.getType() == defaultNetworkInfo.getType()) { |
| // There should not be multiple connected networks of the |
| // same type. At least as of Android Marshmallow this is |
| // not supported. If this becomes supported this assertion |
| // may trigger. At that point we could consider using |
| // ConnectivityManager.getDefaultNetwork() though this |
| // may give confusing results with VPNs and is only |
| // available with Android Marshmallow. |
| assert defaultNetId == INVALID_NET_ID; |
| defaultNetId = networkToNetId(network); |
| } |
| } |
| return defaultNetId; |
| } |
| |
| /** |
| * Returns true if {@code network} can provide Internet access. Can be used to |
| * ignore specialized networks (e.g. IMS, FOTA). |
| */ |
| @SuppressLint("NewApi") |
| boolean hasInternetCapability(Network network) { |
| final NetworkCapabilities capabilities = |
| connectivityManager.getNetworkCapabilities(network); |
| return capabilities != null && capabilities.hasCapability(NET_CAPABILITY_INTERNET); |
| } |
| } |
| |
| /** Queries the WifiManager for SSID of the current Wifi connection. */ |
| static class WifiManagerDelegate { |
| private final Context context; |
| private final WifiManager wifiManager; |
| private final boolean hasWifiPermission; |
| |
| WifiManagerDelegate(Context context) { |
| this.context = context; |
| |
| hasWifiPermission = context.getPackageManager().checkPermission( |
| permission.ACCESS_WIFI_STATE, context.getPackageName()) |
| == PackageManager.PERMISSION_GRANTED; |
| wifiManager = hasWifiPermission |
| ? (WifiManager) context.getSystemService(Context.WIFI_SERVICE) : null; |
| } |
| |
| // For testing. |
| WifiManagerDelegate() { |
| // All the methods below should be overridden. |
| context = null; |
| wifiManager = null; |
| hasWifiPermission = false; |
| } |
| |
| String getWifiSSID() { |
| final Intent intent = context.registerReceiver(null, |
| new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); |
| if (intent != null) { |
| final WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); |
| if (wifiInfo != null) { |
| final String ssid = wifiInfo.getSSID(); |
| if (ssid != null) { |
| return ssid; |
| } |
| } |
| } |
| return ""; |
| } |
| |
| boolean getHasWifiPermission() { |
| return hasWifiPermission; |
| } |
| } |
| |
| static final int INVALID_NET_ID = -1; |
| private static final String TAG = "NetworkMonitorAutoDetect"; |
| private static final int UNKNOWN_LINK_SPEED = -1; |
| private final IntentFilter intentFilter; |
| |
| // Observer for the connection type change. |
| private final Observer observer; |
| |
| private final Context context; |
| // connectivityManagerDelegates and wifiManagerDelegate are only non-final for testing. |
| private ConnectivityManagerDelegate connectivityManagerDelegate; |
| private WifiManagerDelegate wifiManagerDelegate; |
| private boolean isRegistered; |
| private ConnectionType connectionType; |
| private String wifiSSID; |
| |
| /** |
| * Observer interface by which observer is notified of network changes. |
| */ |
| public static interface Observer { |
| /** |
| * Called when default network changes. |
| */ |
| public void onConnectionTypeChanged(ConnectionType newConnectionType); |
| } |
| |
| /** |
| * Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread. |
| */ |
| public NetworkMonitorAutoDetect(Observer observer, Context context) { |
| this.observer = observer; |
| this.context = context; |
| connectivityManagerDelegate = new ConnectivityManagerDelegate(context); |
| wifiManagerDelegate = new WifiManagerDelegate(context); |
| |
| final NetworkState networkState = connectivityManagerDelegate.getNetworkState(); |
| connectionType = getCurrentConnectionType(networkState); |
| wifiSSID = getCurrentWifiSSID(networkState); |
| intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); |
| registerReceiver(); |
| } |
| |
| /** |
| * Allows overriding the ConnectivityManagerDelegate for tests. |
| */ |
| void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) { |
| connectivityManagerDelegate = delegate; |
| } |
| |
| /** |
| * Allows overriding the WifiManagerDelegate for tests. |
| */ |
| void setWifiManagerDelegateForTests(WifiManagerDelegate delegate) { |
| wifiManagerDelegate = delegate; |
| } |
| |
| /** |
| * Returns whether the object has registered to receive network connectivity intents. |
| * Visible for testing. |
| */ |
| boolean isReceiverRegisteredForTesting() { |
| return isRegistered; |
| } |
| |
| public void destroy() { |
| unregisterReceiver(); |
| } |
| |
| /** |
| * Registers a BroadcastReceiver in the given context. |
| */ |
| private void registerReceiver() { |
| if (!isRegistered) { |
| isRegistered = true; |
| context.registerReceiver(this, intentFilter); |
| } |
| } |
| |
| /** |
| * Unregisters the BroadcastReceiver in the given context. |
| */ |
| private void unregisterReceiver() { |
| if (isRegistered) { |
| isRegistered = false; |
| context.unregisterReceiver(this); |
| } |
| } |
| |
| public NetworkState getCurrentNetworkState() { |
| return connectivityManagerDelegate.getNetworkState(); |
| } |
| |
| /** |
| * Returns NetID of device's current default connected network used for |
| * communication. |
| * Only implemented on Lollipop and newer releases, returns INVALID_NET_ID |
| * when not implemented. |
| */ |
| public int getDefaultNetId() { |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { |
| return INVALID_NET_ID; |
| } |
| return connectivityManagerDelegate.getDefaultNetId(); |
| } |
| |
| public ConnectionType getCurrentConnectionType(NetworkState networkState) { |
| if (!networkState.isConnected()) { |
| return ConnectionType.CONNECTION_NONE; |
| } |
| |
| switch (networkState.getNetworkType()) { |
| case ConnectivityManager.TYPE_ETHERNET: |
| return ConnectionType.CONNECTION_ETHERNET; |
| case ConnectivityManager.TYPE_WIFI: |
| return ConnectionType.CONNECTION_WIFI; |
| case ConnectivityManager.TYPE_WIMAX: |
| return ConnectionType.CONNECTION_4G; |
| case ConnectivityManager.TYPE_BLUETOOTH: |
| return ConnectionType.CONNECTION_BLUETOOTH; |
| case ConnectivityManager.TYPE_MOBILE: |
| // Use information from TelephonyManager to classify the connection. |
| switch (networkState.getNetworkSubType()) { |
| case TelephonyManager.NETWORK_TYPE_GPRS: |
| case TelephonyManager.NETWORK_TYPE_EDGE: |
| case TelephonyManager.NETWORK_TYPE_CDMA: |
| case TelephonyManager.NETWORK_TYPE_1xRTT: |
| case TelephonyManager.NETWORK_TYPE_IDEN: |
| return ConnectionType.CONNECTION_2G; |
| case TelephonyManager.NETWORK_TYPE_UMTS: |
| case TelephonyManager.NETWORK_TYPE_EVDO_0: |
| case TelephonyManager.NETWORK_TYPE_EVDO_A: |
| case TelephonyManager.NETWORK_TYPE_HSDPA: |
| case TelephonyManager.NETWORK_TYPE_HSUPA: |
| case TelephonyManager.NETWORK_TYPE_HSPA: |
| case TelephonyManager.NETWORK_TYPE_EVDO_B: |
| case TelephonyManager.NETWORK_TYPE_EHRPD: |
| case TelephonyManager.NETWORK_TYPE_HSPAP: |
| return ConnectionType.CONNECTION_3G; |
| case TelephonyManager.NETWORK_TYPE_LTE: |
| return ConnectionType.CONNECTION_4G; |
| default: |
| return ConnectionType.CONNECTION_UNKNOWN; |
| } |
| default: |
| return ConnectionType.CONNECTION_UNKNOWN; |
| } |
| } |
| |
| private String getCurrentWifiSSID(NetworkState networkState) { |
| if (getCurrentConnectionType(networkState) != ConnectionType.CONNECTION_WIFI) return ""; |
| return wifiManagerDelegate.getWifiSSID(); |
| } |
| |
| // BroadcastReceiver |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final NetworkState networkState = getCurrentNetworkState(); |
| if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { |
| connectionTypeChanged(networkState); |
| } |
| } |
| |
| private void connectionTypeChanged(NetworkState networkState) { |
| ConnectionType newConnectionType = getCurrentConnectionType(networkState); |
| String newWifiSSID = getCurrentWifiSSID(networkState); |
| if (newConnectionType == connectionType && newWifiSSID.equals(wifiSSID)) return; |
| |
| connectionType = newConnectionType; |
| wifiSSID = newWifiSSID; |
| Log.d(TAG, "Network connectivity changed, type is: " + connectionType); |
| observer.onConnectionTypeChanged(newConnectionType); |
| } |
| |
| /** |
| * Extracts NetID of network. Only available on Lollipop and newer releases. |
| */ |
| @SuppressLint("NewApi") |
| private static int networkToNetId(Network network) { |
| // NOTE(pauljensen): This depends on Android framework implementation details. |
| // Fortunately this functionality is unlikely to ever change. |
| // TODO(honghaiz): When we update to Android M SDK, use Network.getNetworkHandle(). |
| return Integer.parseInt(network.toString()); |
| } |
| } |