/* * Copyright 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ package org.webrtc; import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.p2p.WifiP2pGroup; import android.net.wifi.p2p.WifiP2pManager; import android.os.Build; import android.telephony.TelephonyManager; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 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 { public static enum ConnectionType { CONNECTION_UNKNOWN, CONNECTION_ETHERNET, CONNECTION_WIFI, CONNECTION_4G, CONNECTION_3G, CONNECTION_2G, CONNECTION_UNKNOWN_CELLULAR, CONNECTION_BLUETOOTH, CONNECTION_VPN, CONNECTION_NONE } public static class IPAddress { public final byte[] address; public IPAddress(byte[] address) { this.address = address; } @CalledByNative("IPAddress") private byte[] getAddress() { return address; } } /** Java version of NetworkMonitor.NetworkInformation */ public static class NetworkInformation { public final String name; public final ConnectionType type; // Used to specify the underlying network type if the type is CONNECTION_VPN. public final ConnectionType underlyingTypeForVpn; public final long handle; public final IPAddress[] ipAddresses; public NetworkInformation(String name, ConnectionType type, ConnectionType underlyingTypeForVpn, long handle, IPAddress[] addresses) { this.name = name; this.type = type; this.underlyingTypeForVpn = underlyingTypeForVpn; this.handle = handle; this.ipAddresses = addresses; } @CalledByNative("NetworkInformation") private IPAddress[] getIpAddresses() { return ipAddresses; } @CalledByNative("NetworkInformation") private ConnectionType getConnectionType() { return type; } @CalledByNative("NetworkInformation") private ConnectionType getUnderlyingConnectionTypeForVpn() { return underlyingTypeForVpn; } @CalledByNative("NetworkInformation") private long getHandle() { return handle; } @CalledByNative("NetworkInformation") private String getName() { return name; } }; 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; // When the type is TYPE_VPN, the following two fields specify the similar type and subtype as // above for the underlying network that is used by the VPN. private final int underlyingNetworkTypeForVpn; private final int underlyingNetworkSubtypeForVpn; public NetworkState(boolean connected, int type, int subtype, int underlyingNetworkTypeForVpn, int underlyingNetworkSubtypeForVpn) { this.connected = connected; this.type = type; this.subtype = subtype; this.underlyingNetworkTypeForVpn = underlyingNetworkTypeForVpn; this.underlyingNetworkSubtypeForVpn = underlyingNetworkSubtypeForVpn; } public boolean isConnected() { return connected; } public int getNetworkType() { return type; } public int getNetworkSubType() { return subtype; } public int getUnderlyingNetworkTypeForVpn() { return underlyingNetworkTypeForVpn; } public int getUnderlyingNetworkSubtypeForVpn() { return underlyingNetworkSubtypeForVpn; } } /** * The methods in this class get called when the network changes if the callback * is registered with a proper network request. It is only available in Android Lollipop * and above. */ @SuppressLint("NewApi") private class SimpleNetworkCallback extends NetworkCallback { @Override public void onAvailable(Network network) { Logging.d(TAG, "Network becomes available: " + network.toString()); onNetworkChanged(network); } @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { // A capabilities change may indicate the ConnectionType has changed, // so forward the new NetworkInformation along to the observer. Logging.d(TAG, "capabilities changed: " + networkCapabilities.toString()); onNetworkChanged(network); } @Override public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { // A link property change may indicate the IP address changes. // so forward the new NetworkInformation to the observer. Logging.d(TAG, "link properties changed: " + linkProperties.toString()); onNetworkChanged(network); } @Override public void onLosing(Network network, int maxMsToLive) { // Tell the network is going to lose in MaxMsToLive milliseconds. // We may use this signal later. Logging.d( TAG, "Network " + network.toString() + " is about to lose in " + maxMsToLive + "ms"); } @Override public void onLost(Network network) { Logging.d(TAG, "Network " + network.toString() + " is disconnected"); observer.onNetworkDisconnect(networkToNetId(network)); } private void onNetworkChanged(Network network) { NetworkInformation networkInformation = connectivityManagerDelegate.networkToInfo(network); if (networkInformation != null) { observer.onNetworkConnect(networkInformation); } } } /** Queries the ConnectivityManager for information about the current connection. */ static class ConnectivityManagerDelegate { /** * Note: In some rare Android systems connectivityManager is null. We handle that * gracefully below. */ 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() { if (connectivityManager == null) { return new NetworkState(false, -1, -1, -1, -1); } 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) { if (network == null || connectivityManager == null) { return new NetworkState(false, -1, -1, -1, -1); } NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); if (networkInfo == null) { Logging.w(TAG, "Couldn't retrieve information from network " + network.toString()); return new NetworkState(false, -1, -1, -1, -1); } // The general logic of handling a VPN in this method is as follows. getNetworkInfo will // return the info of the network with the same id as in |network| when it is registered via // ConnectivityManager.registerNetworkAgent in Android. |networkInfo| may or may not indicate // the type TYPE_VPN if |network| is a VPN. To reliably detect the VPN interface, we need to // query the network capability as below in the case when networkInfo.getType() is not // TYPE_VPN. On the other hand when networkInfo.getType() is TYPE_VPN, the only solution so // far to obtain the underlying network information is to query the active network interface. // However, the active network interface may not be used for the VPN, for example, if the VPN // is restricted to WiFi by the implementation but the WiFi interface is currently turned // off and the active interface is the Cell. Using directly the result from // getActiveNetworkInfo may thus give the wrong interface information, and one should note // that getActiveNetworkInfo would return the default network interface if the VPN does not // specify its underlying networks in the implementation. Therefore, we need further compare // |network| to the active network. If they are not the same network, we will have to fall // back to report an unknown network. if (networkInfo.getType() != ConnectivityManager.TYPE_VPN) { // Note that getNetworkCapabilities returns null if the network is unknown. NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network); if (networkCapabilities == null || !networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { return getNetworkState(networkInfo); } // When |network| is in fact a VPN after querying its capability but |networkInfo| is not of // type TYPE_VPN, |networkInfo| contains the info for the underlying network, and we return // a NetworkState constructed from it. return new NetworkState(networkInfo.isConnected(), ConnectivityManager.TYPE_VPN, -1, networkInfo.getType(), networkInfo.getSubtype()); } // When |networkInfo| is of type TYPE_VPN, which implies |network| is a VPN, we return the // NetworkState of the active network via getActiveNetworkInfo(), if |network| is the active // network that supports the VPN. Otherwise, NetworkState of an unknown network with type -1 // will be returned. // // Note that getActiveNetwork and getActiveNetworkInfo return null if no default network is // currently active. if (networkInfo.getType() == ConnectivityManager.TYPE_VPN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && network.equals(connectivityManager.getActiveNetwork())) { // If a VPN network is in place, we can find the underlying network type via querying the // active network info thanks to // https://android.googlesource.com/platform/frameworks/base/+/d6a7980d NetworkInfo underlyingActiveNetworkInfo = connectivityManager.getActiveNetworkInfo(); // We use the NetworkInfo of the underlying network if it is not of TYPE_VPN itself. if (underlyingActiveNetworkInfo != null && underlyingActiveNetworkInfo.getType() != ConnectivityManager.TYPE_VPN) { return new NetworkState(networkInfo.isConnected(), ConnectivityManager.TYPE_VPN, -1, underlyingActiveNetworkInfo.getType(), underlyingActiveNetworkInfo.getSubtype()); } } return new NetworkState( networkInfo.isConnected(), ConnectivityManager.TYPE_VPN, -1, -1, -1); } return getNetworkState(networkInfo); } /** * Returns connection type and status information gleaned from networkInfo. Note that to obtain * the complete information about a VPN including the type of the underlying network, one should * use the above method getNetworkState with a Network object. */ private NetworkState getNetworkState( NetworkInfo networkInfo) { if (networkInfo == null || !networkInfo.isConnected()) { return new NetworkState(false, -1, -1, -1, -1); } return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype(), -1, -1); } /** * Returns all connected networks. * Only callable on Lollipop and newer releases. */ @SuppressLint("NewApi") Network[] getAllNetworks() { if (connectivityManager == null) { return new Network[0]; } return connectivityManager.getAllNetworks(); } List<NetworkInformation> getActiveNetworkList() { if (!supportNetworkCallback()) { return null; } ArrayList<NetworkInformation> netInfoList = new ArrayList<NetworkInformation>(); for (Network network : getAllNetworks()) { NetworkInformation info = networkToInfo(network); if (info != null) { netInfoList.add(info); } } return netInfoList; } /** * 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") long getDefaultNetId() { if (!supportNetworkCallback()) { return INVALID_NET_ID; } // 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(); long 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. if (defaultNetId != INVALID_NET_ID) { throw new RuntimeException( "Multiple connected networks of same type are not supported."); } defaultNetId = networkToNetId(network); } } return defaultNetId; } @SuppressLint("NewApi") private NetworkInformation networkToInfo( Network network) { if (network == null || connectivityManager == null) { return null; } LinkProperties linkProperties = connectivityManager.getLinkProperties(network); // getLinkProperties will return null if the network is unknown. if (linkProperties == null) { Logging.w(TAG, "Detected unknown network: " + network.toString()); return null; } if (linkProperties.getInterfaceName() == null) { Logging.w(TAG, "Null interface name for network " + network.toString()); return null; } NetworkState networkState = getNetworkState(network); ConnectionType connectionType = getConnectionType(networkState); if (connectionType == ConnectionType.CONNECTION_NONE) { // This may not be an error. The OS may signal a network event with connection type // NONE when the network disconnects. Logging.d(TAG, "Network " + network.toString() + " is disconnected"); return null; } // Some android device may return a CONNECTION_UNKNOWN_CELLULAR or CONNECTION_UNKNOWN type, // which appears to be usable. Just log them here. if (connectionType == ConnectionType.CONNECTION_UNKNOWN || connectionType == ConnectionType.CONNECTION_UNKNOWN_CELLULAR) { Logging.d(TAG, "Network " + network.toString() + " connection type is " + connectionType + " because it has type " + networkState.getNetworkType() + " and subtype " + networkState.getNetworkSubType()); } // ConnectionType.CONNECTION_UNKNOWN if the network is not a VPN or the underlying network is // unknown. ConnectionType underlyingConnectionTypeForVpn = getUnderlyingConnectionTypeForVpn(networkState); NetworkInformation networkInformation = new NetworkInformation( linkProperties.getInterfaceName(), connectionType, underlyingConnectionTypeForVpn, networkToNetId(network), getIPAddresses(linkProperties)); return networkInformation; } /** * 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) { if (connectivityManager == null) { return false; } final NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); } /** Only callable on Lollipop and newer releases. */ @SuppressLint("NewApi") public void registerNetworkCallback(NetworkCallback networkCallback) { connectivityManager.registerNetworkCallback( new NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(), networkCallback); } /** Only callable on Lollipop and newer releases. */ @SuppressLint("NewApi") public void requestMobileNetwork(NetworkCallback networkCallback) { NetworkRequest.Builder builder = new NetworkRequest.Builder(); builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); connectivityManager.requestNetwork(builder.build(), networkCallback); } @SuppressLint("NewApi") IPAddress[] getIPAddresses(LinkProperties linkProperties) { IPAddress[] ipAddresses = new IPAddress[linkProperties.getLinkAddresses().size()]; int i = 0; for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) { ipAddresses[i] = new IPAddress(linkAddress.getAddress().getAddress()); ++i; } return ipAddresses; } @SuppressLint("NewApi") public void releaseCallback(NetworkCallback networkCallback) { if (supportNetworkCallback()) { Logging.d(TAG, "Unregister network callback"); connectivityManager.unregisterNetworkCallback(networkCallback); } } public boolean supportNetworkCallback() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && connectivityManager != null; } } /** Queries the WifiManager for SSID of the current Wifi connection. */ static class WifiManagerDelegate { private final Context context; WifiManagerDelegate(Context context) { this.context = context; } // For testing. WifiManagerDelegate() { // All the methods below should be overridden. context = null; } 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 ""; } } /** Maintains the information about wifi direct (aka WifiP2p) networks. */ static class WifiDirectManagerDelegate extends BroadcastReceiver { // Network "handle" for the Wifi P2p network. We have to bind to the default network id // (NETWORK_UNSPECIFIED) for these addresses. private static final int WIFI_P2P_NETWORK_HANDLE = 0; private final Context context; private final Observer observer; // Network information about a WifiP2p (aka WiFi-Direct) network, or null if no such network is // connected. private NetworkInformation wifiP2pNetworkInfo; WifiDirectManagerDelegate(Observer observer, Context context) { this.context = context; this.observer = observer; IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); context.registerReceiver(this, intentFilter); } // BroadcastReceiver @Override @SuppressLint("InlinedApi") public void onReceive(Context context, Intent intent) { if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(intent.getAction())) { WifiP2pGroup wifiP2pGroup = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); onWifiP2pGroupChange(wifiP2pGroup); } else if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(intent.getAction())) { int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0 /* default to unknown */); onWifiP2pStateChange(state); } } /** Releases the broadcast receiver. */ public void release() { context.unregisterReceiver(this); } public List<NetworkInformation> getActiveNetworkList() { if (wifiP2pNetworkInfo != null) { return Collections.singletonList(wifiP2pNetworkInfo); } return Collections.emptyList(); } /** Handle a change notification about the wifi p2p group. */ private void onWifiP2pGroupChange( WifiP2pGroup wifiP2pGroup) { if (wifiP2pGroup == null || wifiP2pGroup.getInterface() == null) { return; } NetworkInterface wifiP2pInterface; try { wifiP2pInterface = NetworkInterface.getByName(wifiP2pGroup.getInterface()); } catch (SocketException e) { Logging.e(TAG, "Unable to get WifiP2p network interface", e); return; } List<InetAddress> interfaceAddresses = Collections.list(wifiP2pInterface.getInetAddresses()); IPAddress[] ipAddresses = new IPAddress[interfaceAddresses.size()]; for (int i = 0; i < interfaceAddresses.size(); ++i) { ipAddresses[i] = new IPAddress(interfaceAddresses.get(i).getAddress()); } wifiP2pNetworkInfo = new NetworkInformation(wifiP2pGroup.getInterface(), ConnectionType.CONNECTION_WIFI, ConnectionType.CONNECTION_NONE, WIFI_P2P_NETWORK_HANDLE, ipAddresses); observer.onNetworkConnect(wifiP2pNetworkInfo); } /** Handle a state change notification about wifi p2p. */ private void onWifiP2pStateChange(int state) { if (state == WifiP2pManager.WIFI_P2P_STATE_DISABLED) { wifiP2pNetworkInfo = null; observer.onNetworkDisconnect(WIFI_P2P_NETWORK_HANDLE); } } } static final long INVALID_NET_ID = -1; private static final String TAG = "NetworkMonitorAutoDetect"; // Observer for the connection type change. private final Observer observer; private final IntentFilter intentFilter; private final Context context; // Used to request mobile network. It does not do anything except for keeping // the callback for releasing the request. private final NetworkCallback mobileNetworkCallback; // Used to receive updates on all networks. private final NetworkCallback allNetworkCallback; // connectivityManagerDelegate and wifiManagerDelegate are only non-final for testing. private ConnectivityManagerDelegate connectivityManagerDelegate; private WifiManagerDelegate wifiManagerDelegate; private WifiDirectManagerDelegate wifiDirectManagerDelegate; 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); public void onNetworkConnect(NetworkInformation networkInfo); public void onNetworkDisconnect(long networkHandle); } /** * Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread. */ @SuppressLint("NewApi") 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 = getConnectionType(networkState); wifiSSID = getWifiSSID(networkState); intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); if (PeerConnectionFactory.fieldTrialsFindFullName("IncludeWifiDirect").equals("Enabled")) { wifiDirectManagerDelegate = new WifiDirectManagerDelegate(observer, context); } registerReceiver(); if (connectivityManagerDelegate.supportNetworkCallback()) { // On Android 6.0.0, the WRITE_SETTINGS permission is necessary for // requestNetwork, so it will fail. This was fixed in Android 6.0.1. NetworkCallback tempNetworkCallback = new NetworkCallback(); try { connectivityManagerDelegate.requestMobileNetwork(tempNetworkCallback); } catch (java.lang.SecurityException e) { Logging.w(TAG, "Unable to obtain permission to request a cellular network."); tempNetworkCallback = null; } mobileNetworkCallback = tempNetworkCallback; allNetworkCallback = new SimpleNetworkCallback(); connectivityManagerDelegate.registerNetworkCallback(allNetworkCallback); } else { mobileNetworkCallback = null; allNetworkCallback = null; } } public boolean supportNetworkCallback() { return connectivityManagerDelegate.supportNetworkCallback(); } /** * 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; } List<NetworkInformation> getActiveNetworkList() { List<NetworkInformation> connectivityManagerList = connectivityManagerDelegate.getActiveNetworkList(); if (connectivityManagerList == null) { return null; } ArrayList<NetworkInformation> result = new ArrayList<NetworkInformation>(connectivityManagerList); if (wifiDirectManagerDelegate != null) { result.addAll(wifiDirectManagerDelegate.getActiveNetworkList()); } return result; } public void destroy() { if (allNetworkCallback != null) { connectivityManagerDelegate.releaseCallback(allNetworkCallback); } if (mobileNetworkCallback != null) { connectivityManagerDelegate.releaseCallback(mobileNetworkCallback); } if (wifiDirectManagerDelegate != null) { wifiDirectManagerDelegate.release(); } unregisterReceiver(); } /** * Registers a BroadcastReceiver in the given context. */ private void registerReceiver() { if (isRegistered) return; isRegistered = true; context.registerReceiver(this, intentFilter); } /** * Unregisters the BroadcastReceiver in the given context. */ private void unregisterReceiver() { if (!isRegistered) return; 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 long getDefaultNetId() { return connectivityManagerDelegate.getDefaultNetId(); } private static ConnectionType getConnectionType( boolean isConnected, int networkType, int networkSubtype) { if (!isConnected) { return ConnectionType.CONNECTION_NONE; } switch (networkType) { 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 (networkSubtype) { 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_CELLULAR; } case ConnectivityManager.TYPE_VPN: return ConnectionType.CONNECTION_VPN; default: return ConnectionType.CONNECTION_UNKNOWN; } } public static ConnectionType getConnectionType(NetworkState networkState) { return getConnectionType(networkState.isConnected(), networkState.getNetworkType(), networkState.getNetworkSubType()); } private static ConnectionType getUnderlyingConnectionTypeForVpn(NetworkState networkState) { if (networkState.getNetworkType() != ConnectivityManager.TYPE_VPN) { return ConnectionType.CONNECTION_NONE; } return getConnectionType(networkState.isConnected(), networkState.getUnderlyingNetworkTypeForVpn(), networkState.getUnderlyingNetworkSubtypeForVpn()); } private String getWifiSSID(NetworkState networkState) { if (getConnectionType(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 = getConnectionType(networkState); String newWifiSSID = getWifiSSID(networkState); if (newConnectionType == connectionType && newWifiSSID.equals(wifiSSID)) return; connectionType = newConnectionType; wifiSSID = newWifiSSID; Logging.d(TAG, "Network connectivity changed, type is: " + connectionType); observer.onConnectionTypeChanged(newConnectionType); } /** * Extracts NetID of network on Lollipop and NetworkHandle (which is mungled * NetID) on Marshmallow and newer releases. Only available on Lollipop and * newer releases. Returns long since getNetworkHandle returns long. */ @SuppressLint("NewApi") private static long networkToNetId(Network network) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return network.getNetworkHandle(); } // NOTE(honghaiz): This depends on Android framework implementation details. // These details cannot change because Lollipop has been released. return Integer.parseInt(network.toString()); } }