|  | /* | 
|  | *  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 androidx.annotation.GuardedBy; | 
|  | import androidx.annotation.NonNull; | 
|  | import androidx.annotation.Nullable; | 
|  | import androidx.annotation.VisibleForTesting; | 
|  | import java.net.InetAddress; | 
|  | import java.net.NetworkInterface; | 
|  | import java.net.SocketException; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collections; | 
|  | import java.util.HashSet; | 
|  | import java.util.List; | 
|  | import java.util.Set; | 
|  |  | 
|  | /** | 
|  | * Borrowed from Chromium's | 
|  | * src/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java | 
|  | * | 
|  | * <p>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 implements NetworkChangeDetector { | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | @SuppressLint("NewApi") | 
|  | @VisibleForTesting() | 
|  | class SimpleNetworkCallback extends NetworkCallback { | 
|  | @GuardedBy("availableNetworks") final Set<Network> availableNetworks; | 
|  |  | 
|  | SimpleNetworkCallback(Set<Network> availableNetworks) { | 
|  | this.availableNetworks = availableNetworks; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onAvailable(Network network) { | 
|  | Logging.d(TAG, | 
|  | "Network" | 
|  | + " handle: " + networkToNetId(network) | 
|  | + " becomes available: " + network.toString()); | 
|  |  | 
|  | synchronized (availableNetworks) { | 
|  | availableNetworks.add(network); | 
|  | } | 
|  | 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, | 
|  | "handle: " + networkToNetId(network) | 
|  | + " 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. | 
|  | // | 
|  | // linkProperties.toString() has PII that cannot be redacted | 
|  | // very reliably, so do not include in log. | 
|  | Logging.d(TAG, "handle: " + networkToNetId(network) + " link properties changed"); | 
|  | 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" | 
|  | + " handle: " + networkToNetId(network) + ", " + network.toString() | 
|  | + " is about to lose in " + maxMsToLive + "ms"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onLost(Network network) { | 
|  | Logging.d(TAG, | 
|  | "Network" | 
|  | + " handle: " + networkToNetId(network) + ", " + network.toString() | 
|  | + " is disconnected"); | 
|  |  | 
|  | synchronized (availableNetworks) { | 
|  | availableNetworks.remove(network); | 
|  | } | 
|  | 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. | 
|  | */ | 
|  | @Nullable private final ConnectivityManager connectivityManager; | 
|  |  | 
|  | /** | 
|  | * Note: The availableNetworks set is instantiated in NetworkMonitorAutoDetect | 
|  | * and the instance is mutated by SimpleNetworkCallback. | 
|  | */ | 
|  | @NonNull @GuardedBy("availableNetworks") private final Set<Network> availableNetworks; | 
|  |  | 
|  | /** field trials */ | 
|  | private final boolean getAllNetworksFromCache; | 
|  | private final boolean requestVPN; | 
|  | private final boolean includeOtherUidNetworks; | 
|  |  | 
|  | ConnectivityManagerDelegate( | 
|  | Context context, Set<Network> availableNetworks, String fieldTrialsString) { | 
|  | this((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE), | 
|  | availableNetworks, fieldTrialsString); | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | ConnectivityManagerDelegate(ConnectivityManager connectivityManager, | 
|  | Set<Network> availableNetworks, String fieldTrialsString) { | 
|  | this.connectivityManager = connectivityManager; | 
|  | this.availableNetworks = availableNetworks; | 
|  | this.getAllNetworksFromCache = | 
|  | checkFieldTrial(fieldTrialsString, "getAllNetworksFromCache", false); | 
|  | this.requestVPN = checkFieldTrial(fieldTrialsString, "requestVPN", false); | 
|  | this.includeOtherUidNetworks = | 
|  | checkFieldTrial(fieldTrialsString, "includeOtherUidNetworks", false); | 
|  | } | 
|  |  | 
|  | private static boolean checkFieldTrial( | 
|  | String fieldTrialsString, String key, boolean defaultValue) { | 
|  | if (fieldTrialsString.contains(key + ":true")) { | 
|  | return true; | 
|  | } else if (fieldTrialsString.contains(key + ":false")) { | 
|  | return false; | 
|  | } | 
|  | return defaultValue; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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(@Nullable 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(@Nullable 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]; | 
|  | } | 
|  |  | 
|  | if (supportNetworkCallback() && getAllNetworksFromCache) { | 
|  | synchronized (availableNetworks) { | 
|  | return availableNetworks.toArray(new Network[0]); | 
|  | } | 
|  | } | 
|  |  | 
|  | return connectivityManager.getAllNetworks(); | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | 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 @Nullable NetworkInformation networkToInfo(@Nullable 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); | 
|  | NetworkChangeDetector.ConnectionType connectionType = getConnectionType(networkState); | 
|  | if (connectionType == NetworkChangeDetector.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 == NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN | 
|  | || connectionType == NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN_CELLULAR) { | 
|  | Logging.d(TAG, "Network " + network.toString() + " connection type is " + connectionType | 
|  | + " because it has type " + networkState.getNetworkType() + " and subtype " | 
|  | + networkState.getNetworkSubType()); | 
|  | } | 
|  | // NetworkChangeDetector.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); | 
|  | } | 
|  |  | 
|  | @SuppressLint("NewApi") | 
|  | @VisibleForTesting() | 
|  | NetworkRequest createNetworkRequest() { | 
|  | // Requests the following capabilities by default: NOT_VPN, NOT_RESTRICTED, TRUSTED | 
|  | NetworkRequest.Builder builder = | 
|  | new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); | 
|  |  | 
|  | if (requestVPN) { | 
|  | builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); | 
|  | } | 
|  | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && includeOtherUidNetworks) { | 
|  | builder.setIncludeOtherUidNetworks(true); | 
|  | } | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | /** Only callable on Lollipop and newer releases. */ | 
|  | @SuppressLint("NewApi") | 
|  | public void registerNetworkCallback(NetworkCallback networkCallback) { | 
|  | connectivityManager.registerNetworkCallback(createNetworkRequest(), 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 connectivityManager != null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Queries the WifiManager for SSID of the current Wifi connection. */ | 
|  | static class WifiManagerDelegate { | 
|  | @Nullable 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 NetworkChangeDetector.Observer observer; | 
|  | // Network information about a WifiP2p (aka WiFi-Direct) network, or null if no such network is | 
|  | // connected. | 
|  | @Nullable private NetworkInformation wifiP2pNetworkInfo; | 
|  |  | 
|  | WifiDirectManagerDelegate(NetworkChangeDetector.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); | 
|  | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { | 
|  | // Starting with Android Q (10), WIFI_P2P_CONNECTION_CHANGED_ACTION is no longer sticky. | 
|  | // This means we have to explicitly request WifiP2pGroup info during initialization in order | 
|  | // to get this data if we are already connected to a Wi-Fi Direct network. | 
|  | WifiP2pManager manager = | 
|  | (WifiP2pManager) context.getSystemService(Context.WIFI_P2P_SERVICE); | 
|  | WifiP2pManager.Channel channel = | 
|  | manager.initialize(context, context.getMainLooper(), null /* listener */); | 
|  | manager.requestGroupInfo(channel, wifiP2pGroup -> { onWifiP2pGroupChange(wifiP2pGroup); }); | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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(@Nullable 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(), | 
|  | NetworkChangeDetector.ConnectionType.CONNECTION_WIFI, | 
|  | NetworkChangeDetector.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); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final long INVALID_NET_ID = -1; | 
|  | private static final String TAG = "NetworkMonitorAutoDetect"; | 
|  |  | 
|  | // Observer for the connection type change. | 
|  | private final NetworkChangeDetector.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. | 
|  | @Nullable private final NetworkCallback mobileNetworkCallback; | 
|  | // Used to receive updates on all networks. | 
|  | @Nullable private final NetworkCallback allNetworkCallback; | 
|  | // connectivityManagerDelegate and wifiManagerDelegate are only non-final for testing. | 
|  | private ConnectivityManagerDelegate connectivityManagerDelegate; | 
|  | private WifiManagerDelegate wifiManagerDelegate; | 
|  | private WifiDirectManagerDelegate wifiDirectManagerDelegate; | 
|  | private static boolean includeWifiDirect; | 
|  |  | 
|  | @GuardedBy("availableNetworks") final Set<Network> availableNetworks = new HashSet<>(); | 
|  |  | 
|  | private boolean isRegistered; | 
|  | private NetworkChangeDetector.ConnectionType connectionType; | 
|  | private String wifiSSID; | 
|  |  | 
|  | /** Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread. */ | 
|  | @SuppressLint("NewApi") | 
|  | public NetworkMonitorAutoDetect(NetworkChangeDetector.Observer observer, Context context) { | 
|  | this.observer = observer; | 
|  | this.context = context; | 
|  | String fieldTrialsString = observer.getFieldTrialsString(); | 
|  | connectivityManagerDelegate = | 
|  | new ConnectivityManagerDelegate(context, availableNetworks, fieldTrialsString); | 
|  | wifiManagerDelegate = new WifiManagerDelegate(context); | 
|  |  | 
|  | final NetworkState networkState = connectivityManagerDelegate.getNetworkState(); | 
|  | connectionType = getConnectionType(networkState); | 
|  | wifiSSID = getWifiSSID(networkState); | 
|  | intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); | 
|  |  | 
|  | if (includeWifiDirect) { | 
|  | 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(availableNetworks); | 
|  | connectivityManagerDelegate.registerNetworkCallback(allNetworkCallback); | 
|  | } else { | 
|  | mobileNetworkCallback = null; | 
|  | allNetworkCallback = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Enables WifiDirectManager. */ | 
|  | public static void setIncludeWifiDirect(boolean enable) { | 
|  | includeWifiDirect = enable; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | 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; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public 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; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | 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 NetworkChangeDetector.ConnectionType getConnectionType( | 
|  | boolean isConnected, int networkType, int networkSubtype) { | 
|  | if (!isConnected) { | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_NONE; | 
|  | } | 
|  |  | 
|  | switch (networkType) { | 
|  | case ConnectivityManager.TYPE_ETHERNET: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_ETHERNET; | 
|  | case ConnectivityManager.TYPE_WIFI: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_WIFI; | 
|  | case ConnectivityManager.TYPE_WIMAX: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_4G; | 
|  | case ConnectivityManager.TYPE_BLUETOOTH: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_BLUETOOTH; | 
|  | case ConnectivityManager.TYPE_MOBILE: | 
|  | case ConnectivityManager.TYPE_MOBILE_DUN: | 
|  | case ConnectivityManager.TYPE_MOBILE_HIPRI: | 
|  | // 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: | 
|  | case TelephonyManager.NETWORK_TYPE_GSM: | 
|  | return NetworkChangeDetector.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: | 
|  | case TelephonyManager.NETWORK_TYPE_TD_SCDMA: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_3G; | 
|  | case TelephonyManager.NETWORK_TYPE_LTE: | 
|  | case TelephonyManager.NETWORK_TYPE_IWLAN: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_4G; | 
|  | case TelephonyManager.NETWORK_TYPE_NR: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_5G; | 
|  | default: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN_CELLULAR; | 
|  | } | 
|  | case ConnectivityManager.TYPE_VPN: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_VPN; | 
|  | default: | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_UNKNOWN; | 
|  | } | 
|  | } | 
|  |  | 
|  | public static NetworkChangeDetector.ConnectionType getConnectionType(NetworkState networkState) { | 
|  | return getConnectionType(networkState.isConnected(), networkState.getNetworkType(), | 
|  | networkState.getNetworkSubType()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public NetworkChangeDetector.ConnectionType getCurrentConnectionType() { | 
|  | return getConnectionType(getCurrentNetworkState()); | 
|  | } | 
|  |  | 
|  | private static NetworkChangeDetector.ConnectionType getUnderlyingConnectionTypeForVpn( | 
|  | NetworkState networkState) { | 
|  | if (networkState.getNetworkType() != ConnectivityManager.TYPE_VPN) { | 
|  | return NetworkChangeDetector.ConnectionType.CONNECTION_NONE; | 
|  | } | 
|  | return getConnectionType(networkState.isConnected(), | 
|  | networkState.getUnderlyingNetworkTypeForVpn(), | 
|  | networkState.getUnderlyingNetworkSubtypeForVpn()); | 
|  | } | 
|  |  | 
|  | private String getWifiSSID(NetworkState networkState) { | 
|  | if (getConnectionType(networkState) != NetworkChangeDetector.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) { | 
|  | NetworkChangeDetector.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()); | 
|  | } | 
|  | } |