|
@@ -0,0 +1,870 @@
|
|
|
+/*
|
|
|
+ * 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());
|
|
|
+ }
|
|
|
+}
|