diff options
author | pauljensen <pauljensen@chromium.org> | 2015-10-05 13:39:27 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-05 20:40:07 +0000 |
commit | be5bc3238c657b45c7ff876f3bcd459315ab2b63 (patch) | |
tree | b7f85af7e29fc52338647825ca3cf437f480aae9 | |
parent | dc67237b8beb43a3389c38861c9c153cbb374a91 (diff) | |
download | chromium_src-be5bc3238c657b45c7ff876f3bcd459315ab2b63.zip chromium_src-be5bc3238c657b45c7ff876f3bcd459315ab2b63.tar.gz chromium_src-be5bc3238c657b45c7ff876f3bcd459315ab2b63.tar.bz2 |
NetworkChangeNotifier interface for tracking networks.
Design doc: https://goo.gl/QXm4Kk
Add NetworkChangeNotifier APIs for tracking when individual
networks come and go. This API will be useful for
implementing intelligent QUIC connection migration.
Implementation of the new APIs for Android Lollipop and
newer releases. A ConnectivityManager.NetworkCallback is
registered to monitor when individual networks come and go
in order to calculate when to send the new notification
APIs.
Allow binding UDP sockets to a specific network on Android
Lollipop and newer releases.
Review URL: https://codereview.chromium.org/1340083002
Cr-Commit-Position: refs/heads/master@{#352414}
24 files changed, 1496 insertions, 52 deletions
diff --git a/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java b/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java index 25c2846..390ab99 100644 --- a/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java +++ b/content/public/android/java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java @@ -103,6 +103,14 @@ class BackgroundSyncNetworkObserver implements NetworkChangeNotifierAutoDetect.O @Override public void onMaxBandwidthChanged(double maxBandwidthMbps) {} + @Override + public void onNetworkConnect(int netId, int connectionType) {} + @Override + public void onNetworkSoonToDisconnect(int netId) {} + @Override + public void onNetworkDisconnect(int netId) {} + @Override + public void updateActiveNetworkList(int[] activeNetIds) {} @NativeClassQualifiedName("BackgroundSyncNetworkObserverAndroid::Observer") private native void nativeNotifyConnectionTypeChanged(long nativePtr, int newConnectionType); diff --git a/net/android/BUILD.gn b/net/android/BUILD.gn index 0003962..3344ab7 100644 --- a/net/android/BUILD.gn +++ b/net/android/BUILD.gn @@ -70,6 +70,7 @@ java_cpp_enum("net_android_java_enums_srcjar") { "../base/network_change_notifier.h", "cert_verify_result_android.h", "keystore.h", + "network_change_notifier_android.cc", "traffic_stats.cc", ] outputs = [ @@ -77,6 +78,7 @@ java_cpp_enum("net_android_java_enums_srcjar") { "org/chromium/net/CertVerifyStatusAndroid.java", "org/chromium/net/ConnectionSubtype.java", "org/chromium/net/ConnectionType.java", + "org/chromium/net/NetId.java", "org/chromium/net/PrivateKeyType.java", "org/chromium/net/TrafficStatsError.java", ] diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java index 563ef5c..4008afa 100644 --- a/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java +++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java @@ -78,6 +78,29 @@ public class NetworkChangeNotifier { } /** + * Returns NetID of device's current default connected network used for + * communication. Only available on Lollipop and newer releases and when + * auto-detection has been enabled, returns NetId.INVALID otherwise. + */ + @CalledByNative + public int getCurrentDefaultNetId() { + return mAutoDetector == null ? NetId.INVALID : mAutoDetector.getDefaultNetId(); + } + + /** + * Returns an array of all of the device's currently connected + * networks and ConnectionTypes. Array elements are a repeated sequence of: + * NetID of network + * ConnectionType of network + * Only available on Lollipop and newer releases and when auto-detection has + * been enabled. + */ + @CalledByNative + public int[] getCurrentNetworksAndTypes() { + return mAutoDetector == null ? new int[0] : mAutoDetector.getNetworksAndTypes(); + } + + /** * Calls a native map lookup of subtype to max bandwidth. */ public static double getMaxBandwidthForConnectionSubtype(int subtype) { @@ -153,6 +176,22 @@ public class NetworkChangeNotifier { public void onMaxBandwidthChanged(double maxBandwidthMbps) { updateCurrentMaxBandwidth(maxBandwidthMbps); } + @Override + public void onNetworkConnect(int netId, int connectionType) { + notifyObserversOfNetworkConnect(netId, connectionType); + } + @Override + public void onNetworkSoonToDisconnect(int netId) { + notifyObserversOfNetworkSoonToDisconnect(netId); + } + @Override + public void onNetworkDisconnect(int netId) { + notifyObserversOfNetworkDisconnect(netId); + } + @Override + public void updateActiveNetworkList(int[] activeNetIds) { + notifyObserversToUpdateActiveNetworkList(activeNetIds); + } }, mContext, alwaysWatchForChanges); @@ -188,6 +227,41 @@ public class NetworkChangeNotifier { } } + // For testing, pretend a network connected. + @CalledByNative + public static void fakeNetworkConnected(int netId, int connectionType) { + setAutoDetectConnectivityState(false); + getInstance().notifyObserversOfNetworkConnect(netId, connectionType); + } + + // For testing, pretend a network will soon disconnect. + @CalledByNative + public static void fakeNetworkSoonToBeDisconnected(int netId) { + setAutoDetectConnectivityState(false); + getInstance().notifyObserversOfNetworkSoonToDisconnect(netId); + } + + // For testing, pretend a network disconnected. + @CalledByNative + public static void fakeNetworkDisconnected(int netId) { + setAutoDetectConnectivityState(false); + getInstance().notifyObserversOfNetworkDisconnect(netId); + } + + // For testing, pretend a network lists should be purged. + @CalledByNative + public static void fakeUpdateActiveNetworkList(int[] activeNetIds) { + setAutoDetectConnectivityState(false); + getInstance().notifyObserversToUpdateActiveNetworkList(activeNetIds); + } + + // For testing, pretend a default network changed. + @CalledByNative + public static void fakeDefaultNetwork(int netId, int connectionType) { + setAutoDetectConnectivityState(false); + getInstance().notifyObserversOfConnectionTypeChange(connectionType, netId); + } + private void updateCurrentConnectionType(int newConnectionType) { mCurrentConnectionType = newConnectionType; notifyObserversOfConnectionTypeChange(newConnectionType); @@ -203,8 +277,13 @@ public class NetworkChangeNotifier { * Alerts all observers of a connection change. */ void notifyObserversOfConnectionTypeChange(int newConnectionType) { + notifyObserversOfConnectionTypeChange(newConnectionType, getCurrentDefaultNetId()); + } + + private void notifyObserversOfConnectionTypeChange(int newConnectionType, int defaultNetId) { for (Long nativeChangeNotifier : mNativeChangeNotifiers) { - nativeNotifyConnectionTypeChanged(nativeChangeNotifier, newConnectionType); + nativeNotifyConnectionTypeChanged( + nativeChangeNotifier, newConnectionType, defaultNetId); } for (ConnectionTypeObserver observer : mConnectionTypeObservers) { observer.onConnectionTypeChanged(newConnectionType); @@ -221,6 +300,45 @@ public class NetworkChangeNotifier { } /** + * Alerts all observers of a network connect. + */ + void notifyObserversOfNetworkConnect(int netId, int connectionType) { + for (Long nativeChangeNotifier : mNativeChangeNotifiers) { + nativeNotifyOfNetworkConnect(nativeChangeNotifier, netId, connectionType); + } + } + + /** + * Alerts all observers of a network soon to be disconnected. + */ + void notifyObserversOfNetworkSoonToDisconnect(int netId) { + for (Long nativeChangeNotifier : mNativeChangeNotifiers) { + nativeNotifyOfNetworkSoonToDisconnect(nativeChangeNotifier, netId); + } + } + + /** + * Alerts all observers of a network disconnect. + */ + void notifyObserversOfNetworkDisconnect(int netId) { + for (Long nativeChangeNotifier : mNativeChangeNotifiers) { + nativeNotifyOfNetworkDisconnect(nativeChangeNotifier, netId); + } + } + + /** + * Alerts all observers to purge cached lists of active networks, of any + * networks not in the accompanying list of active networks. This is + * issued if a period elapsed where disconnected notifications may have + * been missed, and acts to keep cached lists of active networks accurate. + */ + void notifyObserversToUpdateActiveNetworkList(int[] activeNetIds) { + for (Long nativeChangeNotifier : mNativeChangeNotifiers) { + nativeNotifyUpdateActiveNetworkList(nativeChangeNotifier, activeNetIds); + } + } + + /** * Adds an observer for any connection type changes. */ public static void addConnectionTypeObserver(ConnectionTypeObserver observer) { @@ -243,11 +361,24 @@ public class NetworkChangeNotifier { } @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") - private native void nativeNotifyConnectionTypeChanged(long nativePtr, int newConnectionType); + private native void nativeNotifyConnectionTypeChanged( + long nativePtr, int newConnectionType, int defaultNetId); @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") private native void nativeNotifyMaxBandwidthChanged(long nativePtr, double maxBandwidthMbps); + @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") + private native void nativeNotifyOfNetworkConnect(long nativePtr, int netId, int connectionType); + + @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") + private native void nativeNotifyOfNetworkSoonToDisconnect(long nativePtr, int netId); + + @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") + private native void nativeNotifyOfNetworkDisconnect(long nativePtr, int netId); + + @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid") + private native void nativeNotifyUpdateActiveNetworkList(long nativePtr, int[] activeNetIds); + private static native double nativeGetMaxBandwidthForConnectionSubtype(int subtype); // For testing only. diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java index 466d03f..839600a 100644 --- a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java +++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java @@ -4,21 +4,30 @@ package org.chromium.net; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; + import android.Manifest.permission; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +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.os.Build; import android.telephony.TelephonyManager; import android.util.Log; import org.chromium.base.ApplicationState; import org.chromium.base.ApplicationStatus; +import org.chromium.base.ThreadUtils; import org.chromium.base.VisibleForTesting; /** @@ -68,13 +77,109 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver mConnectivityManager = null; } + /** + * Returns connection type and status information about the current + * default network. + */ NetworkState getNetworkState() { - final NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); + return getNetworkState(mConnectivityManager.getActiveNetworkInfo()); + } + + /** + * Returns connection type and status information about |network|. + * Only callable on Lollipop and newer releases. + */ + @SuppressLint("NewApi") + NetworkState getNetworkState(Network network) { + return getNetworkState(mConnectivityManager.getNetworkInfo(network)); + } + + /** + * Returns connection type and status information gleaned from networkInfo. + */ + NetworkState getNetworkState(NetworkInfo networkInfo) { if (networkInfo == null || !networkInfo.isConnected()) { return new NetworkState(false, -1, -1); } return new NetworkState(true, networkInfo.getType(), networkInfo.getSubtype()); } + + /** + * Returns all connected networks. + * Only callable on Lollipop and newer releases. + */ + @SuppressLint("NewApi") + Network[] getAllNetworks() { + return mConnectivityManager.getAllNetworks(); + } + + /** + * Registers networkCallback to receive notifications about networks + * that satisfy networkRequest. + * Only callable on Lollipop and newer releases. + */ + @SuppressLint("NewApi") + void registerNetworkCallback( + NetworkRequest networkRequest, NetworkCallback networkCallback) { + mConnectivityManager.registerNetworkCallback(networkRequest, networkCallback); + } + + /** + * Unregisters networkCallback from receiving notifications. + * Only callable on Lollipop and newer releases. + */ + @SuppressLint("NewApi") + void unregisterNetworkCallback(NetworkCallback networkCallback) { + mConnectivityManager.unregisterNetworkCallback(networkCallback); + } + + /** + * Returns the NetID of the current default network. Returns + * NetId.INVALID if no current default network connected. + * Only callable on Lollipop and newer releases. + */ + @SuppressLint("NewApi") + int getDefaultNetId() { + // Android Lollipop had no API to get the default network; only an + // API to return the NetworkInfo for the default network. To + // determine the default network one can find the network with + // type matching that of the default network. + final NetworkInfo defaultNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); + if (defaultNetworkInfo == null) { + return NetId.INVALID; + } + final Network[] networks = getAllNetworks(); + int defaultNetId = NetId.INVALID; + for (Network network : networks) { + if (!hasInternetCapability(network)) { + continue; + } + final NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network); + if (networkInfo != null && networkInfo.getType() == defaultNetworkInfo.getType()) { + // There should not be multiple connected networks of the + // same type. At least as of Android Marshmallow this is + // not supported. If this becomes supported this assertion + // may trigger. At that point we could consider using + // ConnectivityManager.getDefaultNetwork() though this + // may give confusing results with VPNs and is only + // available with Android Marshmallow. + assert defaultNetId == NetId.INVALID; + defaultNetId = networkToNetId(network); + } + } + return defaultNetId; + } + + /** + * Returns true if {@code network} can provide Internet access. Can be used to + * ignore specialized networks (e.g. IMS, FOTA). + */ + @SuppressLint("NewApi") + boolean hasInternetCapability(Network network) { + final NetworkCapabilities capabilities = + mConnectivityManager.getNetworkCapabilities(network); + return capabilities != null && capabilities.hasCapability(NET_CAPABILITY_INTERNET); + } } /** Queries the WifiManager for SSID of the current Wifi connection. */ @@ -137,6 +242,64 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver } } + // This class gets called back by ConnectivityManager whenever networks come + // and go. It gets called back on a special handler thread + // ConnectivityManager creates for making the callbacks. The callbacks in + // turn post to the UI thread where mObserver lives. + @SuppressLint("NewApi") + private class MyNetworkCallback extends NetworkCallback { + @Override + public void onAvailable(Network network) { + final int netId = networkToNetId(network); + final int connectionType = + getCurrentConnectionType(mConnectivityManagerDelegate.getNetworkState(network)); + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + mObserver.onNetworkConnect(netId, connectionType); + } + }); + } + + @Override + public void onCapabilitiesChanged( + Network network, NetworkCapabilities networkCapabilities) { + // A capabilities change may indicate the ConnectionType has changed, + // so forward the new ConnectionType along to observer. + final int netId = networkToNetId(network); + final int connectionType = + getCurrentConnectionType(mConnectivityManagerDelegate.getNetworkState(network)); + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + mObserver.onNetworkConnect(netId, connectionType); + } + }); + } + + @Override + public void onLosing(Network network, int maxMsToLive) { + final int netId = networkToNetId(network); + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + mObserver.onNetworkSoonToDisconnect(netId); + } + }); + } + + @Override + public void onLost(Network network) { + final int netId = networkToNetId(network); + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + mObserver.onNetworkDisconnect(netId); + } + }); + } + } + private static final String TAG = "NetworkChangeNotifierAutoDetect"; private static final int UNKNOWN_LINK_SPEED = -1; private final NetworkConnectivityIntentFilter mIntentFilter; @@ -144,8 +307,12 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver private final Observer mObserver; private final Context mContext; + // mConnectivityManagerDelegates and mWifiManagerDelegate are only non-final for testing. private ConnectivityManagerDelegate mConnectivityManagerDelegate; private WifiManagerDelegate mWifiManagerDelegate; + // mNetworkCallback and mNetworkRequest are only non-null in Android L and above. + private final NetworkCallback mNetworkCallback; + private final NetworkRequest mNetworkRequest; private boolean mRegistered; private final boolean mApplicationStateRegistered; private int mConnectionType; @@ -153,25 +320,71 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver private double mMaxBandwidthMbps; /** - * Observer notified on the UI thread whenever a new connection type was detected or max - * bandwidth is changed. + * Observer interface by which observer is notified of network changes. */ public static interface Observer { + /** + * Called when default network changes. + */ public void onConnectionTypeChanged(int newConnectionType); + /** + * Called when maximum bandwidth of default network changes. + */ public void onMaxBandwidthChanged(double maxBandwidthMbps); + /** + * Called when device connects to network with NetID netId. For + * example device associates with a WiFi access point. + * connectionType is the type of the network; a member of + * ConnectionType. Only called on Android L and above. + */ + public void onNetworkConnect(int netId, int connectionType); + /** + * Called when device determines the connection to the network with + * NetID netId is no longer preferred, for example when a device + * transitions from cellular to WiFi it might deem the cellular + * connection no longer preferred. The device will disconnect from + * the network in 30s allowing network communications on that network + * to wrap up. Only called on Android L and above. + */ + public void onNetworkSoonToDisconnect(int netId); + /** + * Called when device disconnects from network with NetID netId. + * Only called on Android L and above. + */ + public void onNetworkDisconnect(int netId); + /** + * Called to cause a purge of cached lists of active networks, of any + * networks not in the accompanying list of active networks. This is + * issued if a period elapsed where disconnected notifications may have + * been missed, and acts to keep cached lists of active networks + * accurate. Only called on Android L and above. + */ + public void updateActiveNetworkList(int[] activeNetIds); } /** - * Constructs a NetworkChangeNotifierAutoDetect. + * Constructs a NetworkChangeNotifierAutoDetect. Should only be called on UI thread. * @param alwaysWatchForChanges If true, always watch for network changes. * Otherwise, only watch if app is in foreground. */ - public NetworkChangeNotifierAutoDetect(Observer observer, Context context, - boolean alwaysWatchForChanges) { + @SuppressLint("NewApi") + public NetworkChangeNotifierAutoDetect( + Observer observer, Context context, boolean alwaysWatchForChanges) { + // Since BroadcastReceiver is always called back on UI thread, ensure + // running on UI thread so notification logic can be single-threaded. + ThreadUtils.assertOnUiThread(); mObserver = observer; mContext = context.getApplicationContext(); mConnectivityManagerDelegate = new ConnectivityManagerDelegate(context); mWifiManagerDelegate = new WifiManagerDelegate(context); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mNetworkCallback = new MyNetworkCallback(); + mNetworkRequest = + new NetworkRequest.Builder().addCapability(NET_CAPABILITY_INTERNET).build(); + } else { + mNetworkCallback = null; + mNetworkRequest = null; + } final NetworkState networkState = mConnectivityManagerDelegate.getNetworkState(); mConnectionType = getCurrentConnectionType(networkState); mWifiSSID = getCurrentWifiSSID(networkState); @@ -226,22 +439,41 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver } /** - * Register a BroadcastReceiver in the given context. + * Registers a BroadcastReceiver in the given context. */ private void registerReceiver() { if (!mRegistered) { mRegistered = true; mContext.registerReceiver(this, mIntentFilter); + if (mNetworkCallback != null) { + mConnectivityManagerDelegate.registerNetworkCallback( + mNetworkRequest, mNetworkCallback); + // registerNetworkCallback() will rematch our NetworkRequest + // against active networks, so a cached list of active networks + // will be repopulated immediatly after this. However we need to + // purge any cached networks as they may have been disconnected + // while mNetworkCallback was unregistered. + final Network[] networks = mConnectivityManagerDelegate.getAllNetworks(); + // Convert Networks to NetIDs. + final int[] netIds = new int[networks.length]; + for (int i = 0; i < networks.length; i++) { + netIds[i] = networkToNetId(networks[i]); + } + mObserver.updateActiveNetworkList(netIds); + } } } /** - * Unregister the BroadcastReceiver in the given context. + * Unregisters the BroadcastReceiver in the given context. */ private void unregisterReceiver() { if (mRegistered) { mRegistered = false; mContext.unregisterReceiver(this); + if (mNetworkCallback != null) { + mConnectivityManagerDelegate.unregisterNetworkCallback(mNetworkCallback); + } } } @@ -249,6 +481,47 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver return mConnectivityManagerDelegate.getNetworkState(); } + /** + * Returns an array of all of the device's currently connected + * networks and ConnectionTypes. Array elements are a repeated sequence of: + * NetID of network + * ConnectionType of network + * Only available on Lollipop and newer releases and when auto-detection has + * been enabled. + */ + public int[] getNetworksAndTypes() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return new int[0]; + } + final Network networks[] = mConnectivityManagerDelegate.getAllNetworks(); + final int networksAndTypes[] = new int[networks.length * 2]; + int index = 0; + for (Network network : networks) { + if (!mConnectivityManagerDelegate.hasInternetCapability(network)) { + continue; + } + networksAndTypes[index++] = networkToNetId(network); + networksAndTypes[index++] = + getCurrentConnectionType(mConnectivityManagerDelegate.getNetworkState(network)); + } + final int shortenedNetworksAndTypes[] = new int[index]; + System.arraycopy(networksAndTypes, 0, shortenedNetworksAndTypes, 0, index); + return shortenedNetworksAndTypes; + } + + /** + * Returns NetID of device's current default connected network used for + * communication. + * Only implemented on Lollipop and newer releases, returns NetId.INVALID + * when not implemented. + */ + public int getDefaultNetId() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return NetId.INVALID; + } + return mConnectivityManagerDelegate.getDefaultNetId(); + } + public int getCurrentConnectionType(NetworkState networkState) { if (!networkState.isConnected()) { return ConnectionType.CONNECTION_NONE; @@ -293,7 +566,7 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver } /* - * Returns the bandwidth of the current connection in Mbps. The result is + * Returns the bandwidth of the current connection in Mbps. The result is * derived from the NetInfo v3 specification's mapping from network type to * max link speed. In cases where more information is available, such as wifi, * that is used instead. For more on NetInfo, see http://w3c.github.io/netinfo/. @@ -416,4 +689,15 @@ public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver if (monitorRSSI) addAction(WifiManager.RSSI_CHANGED_ACTION); } } + + /** + * Extracts NetID of network. Only available on Lollipop and newer releases. + */ + @SuppressLint("NewApi") + private static int networkToNetId(Network network) { + // NOTE(pauljensen): This depends on Android framework implementation details. + // Fortunately this functionality is unlikely to ever change. + // TODO(pauljensen): When we update to Android M SDK, use Network.getNetworkHandle(). + return Integer.parseInt(network.toString()); + } } diff --git a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java index 0d4608c..b28f5ef 100644 --- a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java +++ b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java @@ -4,24 +4,36 @@ package org.chromium.net; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkRequest; import android.net.wifi.WifiManager; +import android.os.Build; import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; import android.test.UiThreadTest; import android.test.suitebuilder.annotation.MediumTest; import org.chromium.base.ApplicationState; +import org.chromium.base.ThreadUtils; import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.library_loader.LibraryProcessType; import org.chromium.base.test.util.Feature; +import org.chromium.net.NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate; import org.chromium.net.NetworkChangeNotifierAutoDetect.NetworkState; +import java.lang.reflect.Constructor; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; + /** * Tests for org.chromium.net.NetworkChangeNotifier. */ +@SuppressLint("NewApi") public class NetworkChangeNotifierTest extends InstrumentationTestCase { /** * Listens for alerts fired by the NetworkChangeNotifier when network status changes. @@ -47,65 +59,157 @@ public class NetworkChangeNotifierTest extends InstrumentationTestCase { /** * Mocks out calls to the ConnectivityManager. */ - class MockConnectivityManagerDelegate - extends NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate { + private static class MockConnectivityManagerDelegate extends ConnectivityManagerDelegate { private boolean mActiveNetworkExists; private int mNetworkType; private int mNetworkSubtype; + private NetworkCallback mLastRegisteredNetworkCallback; @Override - NetworkState getNetworkState() { + public NetworkState getNetworkState() { return new NetworkState(mActiveNetworkExists, mNetworkType, mNetworkSubtype); } - void setActiveNetworkExists(boolean networkExists) { + // Dummy implementations to avoid NullPointerExceptions in default implementations: + + @Override + public int getDefaultNetId() { + return NetId.INVALID; + } + + @Override + public Network[] getAllNetworks() { + return new Network[0]; + } + + @Override + public NetworkState getNetworkState(Network network) { + return new NetworkState(false, -1, -1); + } + + @Override + public void unregisterNetworkCallback(NetworkCallback networkCallback) {} + + // Dummy implementation that also records the last registered callback. + @Override + public void registerNetworkCallback( + NetworkRequest networkRequest, NetworkCallback networkCallback) { + mLastRegisteredNetworkCallback = networkCallback; + } + + public void setActiveNetworkExists(boolean networkExists) { mActiveNetworkExists = networkExists; } - void setNetworkType(int networkType) { + public void setNetworkType(int networkType) { mNetworkType = networkType; } - void setNetworkSubtype(int networkSubtype) { + public void setNetworkSubtype(int networkSubtype) { mNetworkSubtype = networkSubtype; } + + public NetworkCallback getLastRegisteredNetworkCallback() { + return mLastRegisteredNetworkCallback; + } } /** * Mocks out calls to the WifiManager. */ - class MockWifiManagerDelegate + private static class MockWifiManagerDelegate extends NetworkChangeNotifierAutoDetect.WifiManagerDelegate { private String mWifiSSID; private int mLinkSpeedMbps; @Override - String getWifiSSID() { + public String getWifiSSID() { return mWifiSSID; } - void setWifiSSID(String wifiSSID) { + public void setWifiSSID(String wifiSSID) { mWifiSSID = wifiSSID; } @Override - int getLinkSpeedInMbps() { + public int getLinkSpeedInMbps() { return mLinkSpeedMbps; } - void setLinkSpeedInMbps(int linkSpeedInMbps) { + public void setLinkSpeedInMbps(int linkSpeedInMbps) { mLinkSpeedMbps = linkSpeedInMbps; } } - @Override - protected void setUp() throws Exception { - super.setUp(); - LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER) - .ensureInitialized(getInstrumentation().getTargetContext()); - createTestNotifier(WatchForChanges.ONLY_WHEN_APP_IN_FOREGROUND); + // Types of network changes. Each is associated with a NetworkChangeNotifierAutoDetect.Observer + // callback, and NONE is provided to indicate no callback observed. + private static enum ChangeType { NONE, CONNECT, SOON_TO_DISCONNECT, DISCONNECT, PURGE_LIST } + + // NetworkChangeNotifierAutoDetect.Observer used to verify proper notifications are sent out. + // Notifications come back on UI thread. assertLastChange() called on test thread. + private static class TestNetworkChangeNotifierAutoDetectObserver + implements NetworkChangeNotifierAutoDetect.Observer { + private volatile ChangeType mLastChangeSeen = ChangeType.NONE; + private volatile int mLastNetIdSeen = NetId.INVALID; + + @Override + public void onConnectionTypeChanged(int newConnectionType) {} + @Override + public void onMaxBandwidthChanged(double maxBandwidthMbps) {} + + @Override + public void onNetworkConnect(int netId, int connectionType) { + ThreadUtils.assertOnUiThread(); + assertEquals(mLastChangeSeen, ChangeType.NONE); + assertEquals(mLastNetIdSeen, NetId.INVALID); + mLastChangeSeen = ChangeType.CONNECT; + mLastNetIdSeen = netId; + } + + @Override + public void onNetworkSoonToDisconnect(int netId) { + ThreadUtils.assertOnUiThread(); + assertEquals(mLastChangeSeen, ChangeType.NONE); + assertEquals(mLastNetIdSeen, NetId.INVALID); + mLastChangeSeen = ChangeType.SOON_TO_DISCONNECT; + mLastNetIdSeen = netId; + } + + @Override + public void onNetworkDisconnect(int netId) { + ThreadUtils.assertOnUiThread(); + assertEquals(mLastChangeSeen, ChangeType.NONE); + assertEquals(mLastNetIdSeen, NetId.INVALID); + mLastChangeSeen = ChangeType.DISCONNECT; + mLastNetIdSeen = netId; + } + + @Override + public void updateActiveNetworkList(int[] activeNetIds) { + ThreadUtils.assertOnUiThread(); + assertEquals(mLastChangeSeen, ChangeType.NONE); + assertEquals(mLastNetIdSeen, NetId.INVALID); + mLastChangeSeen = ChangeType.PURGE_LIST; + if (activeNetIds.length >= 1) { + mLastNetIdSeen = activeNetIds[0]; + } else { + mLastNetIdSeen = NetId.INVALID; + } + } + + // Verify last notification was the expected one. + public void assertLastChange(ChangeType type, int netId) throws Exception { + // Make sure notification processed. + flushUiThreadTaskQueue(); + assertEquals(type, mLastChangeSeen); + assertEquals(netId, mLastNetIdSeen); + mLastChangeSeen = ChangeType.NONE; + mLastNetIdSeen = NetId.INVALID; + } } + // Network.Network(int netId) pointer. + private Constructor<Network> mNetworkConstructor; private NetworkChangeNotifierAutoDetect mReceiver; private MockConnectivityManagerDelegate mConnectivityDelegate; private MockWifiManagerDelegate mWifiDelegate; @@ -153,6 +257,36 @@ public class NetworkChangeNotifierTest extends InstrumentationTestCase { return mReceiver.getCurrentConnectionType(networkState); } + // Create Network object given a NetID. + private Network netIdToNetwork(int netId) throws Exception { + return mNetworkConstructor.newInstance(netId); + } + + // Flush UI thread task queue. + private static void flushUiThreadTaskQueue() throws Exception { + FutureTask<Void> task = new FutureTask<Void>(new Runnable() { + public void run() {} + }, null); + ThreadUtils.postOnUiThread(task); + task.get(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER) + .ensureInitialized(getInstrumentation().getTargetContext()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Find Network.Network(int netId) using reflection. + mNetworkConstructor = Network.class.getConstructor(Integer.TYPE); + } + ThreadUtils.postOnUiThread(new Runnable() { + public void run() { + createTestNotifier(WatchForChanges.ONLY_WHEN_APP_IN_FOREGROUND); + } + }); + } + /** * Tests that the receiver registers for connectivity intents during construction. */ @@ -163,12 +297,7 @@ public class NetworkChangeNotifierTest extends InstrumentationTestCase { Context context = getInstrumentation().getTargetContext(); NetworkChangeNotifierAutoDetect.Observer observer = - new NetworkChangeNotifierAutoDetect.Observer() { - @Override - public void onConnectionTypeChanged(int newConnectionType) {} - @Override - public void onMaxBandwidthChanged(double maxBandwidthMbps) {} - }; + new TestNetworkChangeNotifierAutoDetectObserver(); NetworkChangeNotifierAutoDetect receiver = new NetworkChangeNotifierAutoDetect( observer, context, false /* always watch for changes */) { @@ -360,4 +489,122 @@ public class NetworkChangeNotifierTest extends InstrumentationTestCase { mReceiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent); assertTrue(observer.hasReceivedNotification()); } + + /** + * Tests that ConnectivityManagerDelegate doesn't crash. This test cannot rely on having any + * active network connections so it cannot usefully check results, but it can at least check + * that the functions don't crash. + */ + @UiThreadTest + @MediumTest + @Feature({"Android-AppBase"}) + public void testConnectivityManagerDelegateDoesNotCrash() { + ConnectivityManagerDelegate delegate = + new ConnectivityManagerDelegate(getInstrumentation().getTargetContext()); + delegate.getNetworkState(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Network[] networks = delegate.getAllNetworks(); + if (networks.length >= 1) { + delegate.getNetworkState(networks[0]); + delegate.hasInternetCapability(networks[0]); + } + delegate.getDefaultNetId(); + NetworkCallback networkCallback = new NetworkCallback(); + NetworkRequest networkRequest = new NetworkRequest.Builder().build(); + delegate.registerNetworkCallback(networkRequest, networkCallback); + delegate.unregisterNetworkCallback(networkCallback); + } + } + + /** + * Tests that NetworkChangeNotifierAutoDetect queryable APIs don't crash. This test cannot rely + * on having any active network connections so it cannot usefully check results, but it can at + * least check that the functions don't crash. + */ + @UiThreadTest + @MediumTest + @Feature({"Android-AppBase"}) + public void testQueryableAPIsDoNotCrash() { + NetworkChangeNotifierAutoDetect.Observer observer = + new TestNetworkChangeNotifierAutoDetectObserver(); + NetworkChangeNotifierAutoDetect ncn = new NetworkChangeNotifierAutoDetect( + observer, getInstrumentation().getTargetContext(), true); + ncn.getNetworksAndTypes(); + ncn.getDefaultNetId(); + } + + /** + * Tests that callbacks are issued to Observers when NetworkChangeNotifierAutoDetect receives + * the right signals (via its NetworkCallback). + */ + @MediumTest + @Feature({"Android-AppBase"}) + public void testNetworkCallbacks() throws Exception { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return; + } + // Setup NetworkChangeNotifierAutoDetect + final Context context = getInstrumentation().getTargetContext(); + final TestNetworkChangeNotifierAutoDetectObserver observer = + new TestNetworkChangeNotifierAutoDetectObserver(); + Callable<NetworkChangeNotifierAutoDetect> callable = + new Callable<NetworkChangeNotifierAutoDetect>() { + public NetworkChangeNotifierAutoDetect call() { + return new NetworkChangeNotifierAutoDetect( + observer, context, false /* always watch for changes */) { + // This override prevents NetworkChangeNotifierAutoDetect from + // registering for events right off the bat. We'll delay this + // until our MockConnectivityManagerDelegate is first installed + // to prevent inadvertent communication with the real + // ConnectivityManager. + @Override + int getApplicationState() { + return ApplicationState.HAS_PAUSED_ACTIVITIES; + } + }; + } + }; + FutureTask<NetworkChangeNotifierAutoDetect> task = + new FutureTask<NetworkChangeNotifierAutoDetect>(callable); + ThreadUtils.postOnUiThread(task); + NetworkChangeNotifierAutoDetect ncn = task.get(); + + // Insert mock ConnectivityDelegate + mConnectivityDelegate = new MockConnectivityManagerDelegate(); + ncn.setConnectivityManagerDelegateForTests(mConnectivityDelegate); + // Now that mock ConnectivityDelegate is inserted, pretend app is foregrounded + // so NetworkChangeNotifierAutoDetect will register its NetworkCallback. + assertFalse(ncn.isReceiverRegisteredForTesting()); + ncn.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES); + assertTrue(ncn.isReceiverRegisteredForTesting()); + + // Find NetworkChangeNotifierAutoDetect's NetworkCallback, which should have been registered + // with mConnectivityDelegate. + NetworkCallback networkCallback = mConnectivityDelegate.getLastRegisteredNetworkCallback(); + assertNotNull(networkCallback); + + // First thing we'll receive is a purge to initialize any network lists. + observer.assertLastChange(ChangeType.PURGE_LIST, NetId.INVALID); + + // Test connected signal is passed along. + networkCallback.onAvailable(netIdToNetwork(100)); + observer.assertLastChange(ChangeType.CONNECT, 100); + + // Test soon-to-be-disconnected signal is passed along. + networkCallback.onLosing(netIdToNetwork(101), 30); + observer.assertLastChange(ChangeType.SOON_TO_DISCONNECT, 101); + + // Test connected signal is passed along. + networkCallback.onLost(netIdToNetwork(102)); + observer.assertLastChange(ChangeType.DISCONNECT, 102); + + // Simulate app backgrounding then foregrounding. + assertTrue(ncn.isReceiverRegisteredForTesting()); + ncn.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES); + assertFalse(ncn.isReceiverRegisteredForTesting()); + ncn.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES); + assertTrue(ncn.isReceiverRegisteredForTesting()); + // Verify network list purged. + observer.assertLastChange(ChangeType.PURGE_LIST, NetId.INVALID); + } } diff --git a/net/android/network_change_notifier_android.cc b/net/android/network_change_notifier_android.cc index a967b85..eac6589 100644 --- a/net/android/network_change_notifier_android.cc +++ b/net/android/network_change_notifier_android.cc @@ -65,6 +65,17 @@ namespace net { +// Expose kInvalidNetworkHandle out to Java as NetId.INVALID. The notion of +// a NetID is an Android framework one, see android.net.Network.netId. +// NetworkChangeNotifierAndroid implements NetworkHandle to simply be the NetID. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.net +enum NetId { + // Cannot use |kInvalidNetworkHandle| here as the Java generator fails, + // instead enforce their equality with CHECK in + // NetworkChangeNotifierAndroid(). + INVALID = -1 +}; + // Thread on which we can run DnsConfigService, which requires a TYPE_IO // message loop to monitor /system/etc/hosts. class NetworkChangeNotifierAndroid::DnsConfigServiceThread @@ -151,6 +162,22 @@ void NetworkChangeNotifierAndroid::GetCurrentMaxBandwidthAndConnectionType( connection_type); } +void NetworkChangeNotifierAndroid::GetCurrentConnectedNetworks( + NetworkChangeNotifier::NetworkList* networks) const { + delegate_->GetCurrentlyConnectedNetworks(networks); +} + +NetworkChangeNotifier::ConnectionType +NetworkChangeNotifierAndroid::GetCurrentNetworkConnectionType( + NetworkHandle network) const { + return delegate_->GetNetworkConnectionType(network); +} + +NetworkChangeNotifier::NetworkHandle +NetworkChangeNotifierAndroid::GetCurrentDefaultNetwork() const { + return delegate_->GetCurrentDefaultNetwork(); +} + void NetworkChangeNotifierAndroid::OnConnectionTypeChanged() { DnsConfigServiceThread::NotifyNetworkChangeNotifierObservers(); } @@ -162,6 +189,28 @@ void NetworkChangeNotifierAndroid::OnMaxBandwidthChanged( type); } +void NetworkChangeNotifierAndroid::OnNetworkConnected(NetworkHandle network) { + NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChange( + NetworkChangeType::CONNECTED, network); +} + +void NetworkChangeNotifierAndroid::OnNetworkSoonToDisconnect( + NetworkHandle network) { + NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChange( + NetworkChangeType::SOON_TO_DISCONNECT, network); +} + +void NetworkChangeNotifierAndroid::OnNetworkDisconnected( + NetworkHandle network) { + NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChange( + NetworkChangeType::DISCONNECTED, network); +} + +void NetworkChangeNotifierAndroid::OnNetworkMadeDefault(NetworkHandle network) { + NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChange( + NetworkChangeType::MADE_DEFAULT, network); +} + // static bool NetworkChangeNotifierAndroid::Register(JNIEnv* env) { return NetworkChangeNotifierDelegateAndroid::Register(env); @@ -174,6 +223,8 @@ NetworkChangeNotifierAndroid::NetworkChangeNotifierAndroid( delegate_(delegate), dns_config_service_thread_( new DnsConfigServiceThread(dns_config_for_testing)) { + CHECK_EQ(NetId::INVALID, NetworkChangeNotifier::kInvalidNetworkHandle) + << "kInvalidNetworkHandle doesn't match NetId::INVALID"; delegate_->AddObserver(this); dns_config_service_thread_->StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); diff --git a/net/android/network_change_notifier_android.h b/net/android/network_change_notifier_android.h index 61bec53..b4870bf 100644 --- a/net/android/network_change_notifier_android.h +++ b/net/android/network_change_notifier_android.h @@ -52,11 +52,19 @@ class NET_EXPORT_PRIVATE NetworkChangeNotifierAndroid void GetCurrentMaxBandwidthAndConnectionType( double* max_bandwidth_mbps, ConnectionType* connection_type) const override; + void GetCurrentConnectedNetworks(NetworkList* network_list) const override; + ConnectionType GetCurrentNetworkConnectionType( + NetworkHandle network) const override; + NetworkHandle GetCurrentDefaultNetwork() const override; // NetworkChangeNotifierDelegateAndroid::Observer: void OnConnectionTypeChanged() override; void OnMaxBandwidthChanged(double max_bandwidth_mbps, ConnectionType type) override; + void OnNetworkConnected(NetworkHandle network) override; + void OnNetworkSoonToDisconnect(NetworkHandle network) override; + void OnNetworkDisconnected(NetworkHandle network) override; + void OnNetworkMadeDefault(NetworkHandle network) override; static bool Register(JNIEnv* env); diff --git a/net/android/network_change_notifier_android_unittest.cc b/net/android/network_change_notifier_android_unittest.cc index d3fe04d..68b6d43 100644 --- a/net/android/network_change_notifier_android_unittest.cc +++ b/net/android/network_change_notifier_android_unittest.cc @@ -19,9 +19,23 @@ namespace net { namespace { +// Types of network changes. See similarly named functions in +// NetworkChangeNotifier::NetworkObserver for descriptions. +enum ChangeType { + NONE, + CONNECTED, + SOON_TO_DISCONNECT, + DISCONNECTED, + MADE_DEFAULT, +}; + class NetworkChangeNotifierDelegateAndroidObserver : public NetworkChangeNotifierDelegateAndroid::Observer { public: + typedef NetworkChangeNotifier::ConnectionType ConnectionType; + typedef NetworkChangeNotifier::NetworkHandle NetworkHandle; + typedef NetworkChangeNotifier::NetworkList NetworkList; + NetworkChangeNotifierDelegateAndroidObserver() : type_notifications_count_(0), max_bandwidth_notifications_count_(0) {} @@ -34,6 +48,14 @@ class NetworkChangeNotifierDelegateAndroidObserver max_bandwidth_notifications_count_++; } + void OnNetworkConnected(NetworkHandle network) override {} + + void OnNetworkSoonToDisconnect(NetworkHandle network) override {} + + void OnNetworkDisconnected(NetworkHandle network) override {} + + void OnNetworkMadeDefault(NetworkHandle network) override {} + int type_notifications_count() const { return type_notifications_count_; } int bandwidth_notifications_count() const { return max_bandwidth_notifications_count_; @@ -87,6 +109,55 @@ class DNSChangeObserver : public NetworkChangeNotifier::DNSObserver { int initial_notifications_count_; }; +// A NetworkObserver used for verifying correct notifications are sent. +class TestNetworkObserver : public NetworkChangeNotifier::NetworkObserver { + public: + TestNetworkObserver() { Clear(); } + + void ExpectChange(ChangeType change, + NetworkChangeNotifier::NetworkHandle network) { + EXPECT_EQ(last_change_type_, change); + EXPECT_EQ(last_network_changed_, network); + Clear(); + } + + private: + void Clear() { + last_change_type_ = NONE; + last_network_changed_ = NetworkChangeNotifier::kInvalidNetworkHandle; + } + + // NetworkChangeNotifier::NetworkObserver implementation: + void OnNetworkConnected( + NetworkChangeNotifier::NetworkHandle network) override { + ExpectChange(NONE, NetworkChangeNotifier::kInvalidNetworkHandle); + last_change_type_ = CONNECTED; + last_network_changed_ = network; + } + void OnNetworkSoonToDisconnect( + NetworkChangeNotifier::NetworkHandle network) override { + ExpectChange(NONE, NetworkChangeNotifier::kInvalidNetworkHandle); + last_change_type_ = SOON_TO_DISCONNECT; + last_network_changed_ = network; + } + void OnNetworkDisconnected( + NetworkChangeNotifier::NetworkHandle network) override { + ExpectChange(NONE, NetworkChangeNotifier::kInvalidNetworkHandle); + last_change_type_ = DISCONNECTED; + last_network_changed_ = network; + } + void OnNetworkMadeDefault( + NetworkChangeNotifier::NetworkHandle network) override { + // Cannot test for Clear()ed state as we receive CONNECTED immediately prior + // to MADE_DEFAULT. + last_change_type_ = MADE_DEFAULT; + last_network_changed_ = network; + } + + ChangeType last_change_type_; + NetworkChangeNotifier::NetworkHandle last_network_changed_; +}; + } // namespace class BaseNetworkChangeNotifierAndroidTest : public testing::Test { @@ -135,6 +206,37 @@ class BaseNetworkChangeNotifierAndroidTest : public testing::Test { base::MessageLoop::current()->RunUntilIdle(); } + void FakeNetworkChange(ChangeType change, + NetworkChangeNotifier::NetworkHandle network, + ConnectionType type) { + switch (change) { + case CONNECTED: + delegate_.FakeNetworkConnected(network, type); + break; + case SOON_TO_DISCONNECT: + delegate_.FakeNetworkSoonToBeDisconnected(network); + break; + case DISCONNECTED: + delegate_.FakeNetworkDisconnected(network); + break; + case MADE_DEFAULT: + delegate_.FakeDefaultNetwork(network, type); + break; + case NONE: + NOTREACHED(); + break; + } + // See comment above. + base::MessageLoop::current()->RunUntilIdle(); + } + + void FakeUpdateActiveNetworkList( + NetworkChangeNotifier::NetworkList networks) { + delegate_.FakeUpdateActiveNetworkList(networks); + // See comment above. + base::MessageLoop::current()->RunUntilIdle(); + } + NetworkChangeNotifierDelegateAndroid delegate_; }; @@ -284,4 +386,99 @@ TEST_F(NetworkChangeNotifierAndroidTest, InitialSignal) { NetworkChangeNotifier::RemoveDNSObserver(&dns_change_observer); } +TEST_F(NetworkChangeNotifierAndroidTest, NetworkCallbacks) { + TestNetworkObserver network_observer; + NetworkChangeNotifier::AddNetworkObserver(&network_observer); + + // Test empty values + EXPECT_EQ(NetworkChangeNotifier::kInvalidNetworkHandle, + NetworkChangeNotifier::GetDefaultNetwork()); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN, + NetworkChangeNotifier::GetNetworkConnectionType(100)); + NetworkChangeNotifier::NetworkList network_list; + NetworkChangeNotifier::GetConnectedNetworks(&network_list); + EXPECT_EQ(0u, network_list.size()); + // Test connecting network + FakeNetworkChange(CONNECTED, 100, NetworkChangeNotifier::CONNECTION_WIFI); + network_observer.ExpectChange(CONNECTED, 100); + EXPECT_EQ(NetworkChangeNotifier::kInvalidNetworkHandle, + NetworkChangeNotifier::GetDefaultNetwork()); + // Test GetConnectedNetworks() + NetworkChangeNotifier::GetConnectedNetworks(&network_list); + EXPECT_EQ(1u, network_list.size()); + EXPECT_EQ(100, network_list[0]); + // Test GetNetworkConnectionType() + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_WIFI, + NetworkChangeNotifier::GetNetworkConnectionType(100)); + // Test deduplication of connecting signal + FakeNetworkChange(CONNECTED, 100, NetworkChangeNotifier::CONNECTION_WIFI); + network_observer.ExpectChange(NONE, + NetworkChangeNotifier::kInvalidNetworkHandle); + // Test connecting another network + FakeNetworkChange(CONNECTED, 101, NetworkChangeNotifier::CONNECTION_3G); + network_observer.ExpectChange(CONNECTED, 101); + NetworkChangeNotifier::GetConnectedNetworks(&network_list); + EXPECT_EQ(2u, network_list.size()); + EXPECT_EQ(100, network_list[0]); + EXPECT_EQ(101, network_list[1]); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_WIFI, + NetworkChangeNotifier::GetNetworkConnectionType(100)); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_3G, + NetworkChangeNotifier::GetNetworkConnectionType(101)); + // Test lingering network + FakeNetworkChange(SOON_TO_DISCONNECT, 100, + NetworkChangeNotifier::CONNECTION_WIFI); + network_observer.ExpectChange(SOON_TO_DISCONNECT, 100); + NetworkChangeNotifier::GetConnectedNetworks(&network_list); + EXPECT_EQ(2u, network_list.size()); + EXPECT_EQ(100, network_list[0]); + EXPECT_EQ(101, network_list[1]); + // Test disconnecting network + FakeNetworkChange(DISCONNECTED, 100, NetworkChangeNotifier::CONNECTION_WIFI); + network_observer.ExpectChange(DISCONNECTED, 100); + NetworkChangeNotifier::GetConnectedNetworks(&network_list); + EXPECT_EQ(1u, network_list.size()); + EXPECT_EQ(101, network_list[0]); + // Test deduplication of disconnecting signal + FakeNetworkChange(DISCONNECTED, 100, NetworkChangeNotifier::CONNECTION_WIFI); + network_observer.ExpectChange(NONE, + NetworkChangeNotifier::kInvalidNetworkHandle); + // Test delay of default network signal until connect signal + FakeNetworkChange(MADE_DEFAULT, 100, NetworkChangeNotifier::CONNECTION_WIFI); + network_observer.ExpectChange(NONE, + NetworkChangeNotifier::kInvalidNetworkHandle); + FakeNetworkChange(CONNECTED, 100, NetworkChangeNotifier::CONNECTION_WIFI); + network_observer.ExpectChange(MADE_DEFAULT, 100); + EXPECT_EQ(100, NetworkChangeNotifier::GetDefaultNetwork()); + // Test change of default + FakeNetworkChange(MADE_DEFAULT, 101, NetworkChangeNotifier::CONNECTION_3G); + network_observer.ExpectChange(MADE_DEFAULT, 101); + EXPECT_EQ(101, NetworkChangeNotifier::GetDefaultNetwork()); + // Test deduplication default signal + FakeNetworkChange(MADE_DEFAULT, 101, NetworkChangeNotifier::CONNECTION_3G); + network_observer.ExpectChange(NONE, + NetworkChangeNotifier::kInvalidNetworkHandle); + // Test that networks can change type + FakeNetworkChange(CONNECTED, 101, NetworkChangeNotifier::CONNECTION_4G); + network_observer.ExpectChange(NONE, + NetworkChangeNotifier::kInvalidNetworkHandle); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_4G, + NetworkChangeNotifier::GetNetworkConnectionType(101)); + // Test purging the network list + NetworkChangeNotifier::GetConnectedNetworks(&network_list); + EXPECT_EQ(2u, network_list.size()); + EXPECT_EQ(100, network_list[0]); + EXPECT_EQ(101, network_list[1]); + network_list.erase(network_list.begin() + 1); // Remove network 101 + FakeUpdateActiveNetworkList(network_list); + network_observer.ExpectChange(DISCONNECTED, 101); + NetworkChangeNotifier::GetConnectedNetworks(&network_list); + EXPECT_EQ(1u, network_list.size()); + EXPECT_EQ(100, network_list[0]); + EXPECT_EQ(NetworkChangeNotifier::kInvalidNetworkHandle, + NetworkChangeNotifier::GetDefaultNetwork()); + + NetworkChangeNotifier::RemoveNetworkObserver(&network_observer); +} + } // namespace net diff --git a/net/android/network_change_notifier_delegate_android.cc b/net/android/network_change_notifier_delegate_android.cc index be4c6b1..ea2ba93 100644 --- a/net/android/network_change_notifier_delegate_android.cc +++ b/net/android/network_change_notifier_delegate_android.cc @@ -4,6 +4,7 @@ #include "net/android/network_change_notifier_delegate_android.h" +#include "base/android/jni_array.h" #include "base/logging.h" #include "jni/NetworkChangeNotifier_jni.h" #include "net/android/network_change_notifier_android.h" @@ -44,6 +45,21 @@ NetworkChangeNotifier::ConnectionSubtype ConvertConnectionSubtype( } // namespace +// static +void NetworkChangeNotifierDelegateAndroid::JavaIntArrayToNetworkMap( + JNIEnv* env, + jintArray int_array, + NetworkMap* network_map) { + std::vector<int> int_list; + base::android::JavaIntArrayToIntVector(env, int_array, &int_list); + network_map->clear(); + for (auto i = int_list.begin(); i != int_list.end(); ++i) { + NetworkChangeNotifier::NetworkHandle network_handle = *i; + CHECK(++i != int_list.end()); + (*network_map)[network_handle] = static_cast<ConnectionType>(*i); + } +} + jdouble GetMaxBandwidthForConnectionSubtype(JNIEnv* env, const JavaParamRef<jclass>& caller, jint subtype) { @@ -67,6 +83,14 @@ NetworkChangeNotifierDelegateAndroid::NetworkChangeNotifierDelegateAndroid() SetCurrentMaxBandwidth( Java_NetworkChangeNotifier_getCurrentMaxBandwidthInMbps( env, java_network_change_notifier_.obj())); + SetCurrentDefaultNetwork(Java_NetworkChangeNotifier_getCurrentDefaultNetId( + env, java_network_change_notifier_.obj())); + NetworkMap network_map; + ScopedJavaLocalRef<jintArray> networks_and_types = + Java_NetworkChangeNotifier_getCurrentNetworksAndTypes( + env, java_network_change_notifier_.obj()); + JavaIntArrayToNetworkMap(env, networks_and_types.obj(), &network_map); + SetCurrentNetworksAndTypes(network_map); } NetworkChangeNotifierDelegateAndroid::~NetworkChangeNotifierDelegateAndroid() { @@ -93,14 +117,60 @@ void NetworkChangeNotifierDelegateAndroid:: *max_bandwidth_mbps = connection_max_bandwidth_; } +NetworkChangeNotifier::ConnectionType +NetworkChangeNotifierDelegateAndroid::GetNetworkConnectionType( + NetworkChangeNotifier::NetworkHandle network) const { + base::AutoLock auto_lock(connection_lock_); + auto network_entry = network_map_.find(network); + if (network_entry == network_map_.end()) + return ConnectionType::CONNECTION_UNKNOWN; + return network_entry->second; +} + +NetworkChangeNotifier::NetworkHandle +NetworkChangeNotifierDelegateAndroid::GetCurrentDefaultNetwork() const { + base::AutoLock auto_lock(connection_lock_); + return default_network_; +} + +void NetworkChangeNotifierDelegateAndroid::GetCurrentlyConnectedNetworks( + NetworkList* network_list) const { + network_list->clear(); + base::AutoLock auto_lock(connection_lock_); + for (auto i : network_map_) + network_list->push_back(i.first); +} + void NetworkChangeNotifierDelegateAndroid::NotifyConnectionTypeChanged( JNIEnv* env, jobject obj, - jint new_connection_type) { + jint new_connection_type, + jint default_netid) { DCHECK(thread_checker_.CalledOnValidThread()); const ConnectionType actual_connection_type = ConvertConnectionType( new_connection_type); SetCurrentConnectionType(actual_connection_type); + NetworkHandle default_network = default_netid; + if (default_network != GetCurrentDefaultNetwork()) { + SetCurrentDefaultNetwork(default_network); + bool default_exists; + { + base::AutoLock auto_lock(connection_lock_); + // |default_network| may be an invalid value (i.e. -1) in cases where + // the device is disconnected or when run on Android versions prior to L, + // in which case |default_exists| will correctly be false and no + // OnNetworkMadeDefault notification will be sent. + default_exists = network_map_.find(default_network) != network_map_.end(); + } + // Android Lollipop had race conditions where CONNECTIVITY_ACTION intents + // were sent out before the network was actually made the default. + // Delay sending the OnNetworkMadeDefault notification until we are + // actually notified that the network connected in NotifyOfNetworkConnect. + if (default_exists) { + observers_->Notify(FROM_HERE, &Observer::OnNetworkMadeDefault, + default_network); + } + } observers_->Notify(FROM_HERE, &Observer::OnConnectionTypeChanged); } @@ -121,6 +191,88 @@ void NetworkChangeNotifierDelegateAndroid::NotifyMaxBandwidthChanged( new_max_bandwidth, GetCurrentConnectionType()); } +void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkConnect( + JNIEnv* env, + jobject obj, + jint net_id, + jint connection_type) { + DCHECK(thread_checker_.CalledOnValidThread()); + NetworkHandle network = net_id; + bool already_exists; + { + base::AutoLock auto_lock(connection_lock_); + already_exists = network_map_.find(network) != network_map_.end(); + network_map_[network] = static_cast<ConnectionType>(connection_type); + } + // Android Lollipop would send many duplicate notifications. + // This was later fixed in Android Marshmallow. + // Deduplicate them here by avoiding sending duplicate notifications. + if (!already_exists) { + observers_->Notify(FROM_HERE, &Observer::OnNetworkConnected, network); + if (network == GetCurrentDefaultNetwork()) { + observers_->Notify(FROM_HERE, &Observer::OnNetworkMadeDefault, network); + } + } +} + +void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkSoonToDisconnect( + JNIEnv* env, + jobject obj, + jint net_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + NetworkHandle network = net_id; + { + base::AutoLock auto_lock(connection_lock_); + if (network_map_.find(network) == network_map_.end()) + return; + } + observers_->Notify(FROM_HERE, &Observer::OnNetworkSoonToDisconnect, network); +} + +void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkDisconnect( + JNIEnv* env, + jobject obj, + jint net_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + NetworkHandle network = net_id; + { + base::AutoLock auto_lock(connection_lock_); + if (network == default_network_) + default_network_ = NetworkChangeNotifier::kInvalidNetworkHandle; + if (network_map_.erase(network) == 0) + return; + } + observers_->Notify(FROM_HERE, &Observer::OnNetworkDisconnected, network); +} + +void NetworkChangeNotifierDelegateAndroid::NotifyUpdateActiveNetworkList( + JNIEnv* env, + jobject obj, + jintArray active_networks) { + DCHECK(thread_checker_.CalledOnValidThread()); + NetworkList active_network_list; + base::android::JavaIntArrayToIntVector(env, active_networks, + &active_network_list); + NetworkList disconnected_networks; + { + base::AutoLock auto_lock(connection_lock_); + for (auto i : network_map_) { + bool found = false; + for (auto j : active_network_list) { + if (j == i.first) { + found = true; + break; + } + } + if (!found) { + disconnected_networks.push_back(i.first); + } + } + } + for (auto disconnected_network : disconnected_networks) + NotifyOfNetworkDisconnect(env, obj, disconnected_network); +} + void NetworkChangeNotifierDelegateAndroid::AddObserver( Observer* observer) { observers_->AddObserver(observer); @@ -148,6 +300,18 @@ void NetworkChangeNotifierDelegateAndroid::SetCurrentMaxBandwidth( connection_max_bandwidth_ = max_bandwidth; } +void NetworkChangeNotifierDelegateAndroid::SetCurrentDefaultNetwork( + NetworkHandle default_network) { + base::AutoLock auto_lock(connection_lock_); + default_network_ = default_network; +} + +void NetworkChangeNotifierDelegateAndroid::SetCurrentNetworksAndTypes( + NetworkMap network_map) { + base::AutoLock auto_lock(connection_lock_); + network_map_ = network_map; +} + void NetworkChangeNotifierDelegateAndroid::SetOnline() { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_forceConnectivityState(env, true); @@ -158,4 +322,37 @@ void NetworkChangeNotifierDelegateAndroid::SetOffline() { Java_NetworkChangeNotifier_forceConnectivityState(env, false); } +void NetworkChangeNotifierDelegateAndroid::FakeNetworkConnected( + NetworkChangeNotifier::NetworkHandle network, + ConnectionType type) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_fakeNetworkConnected(env, network, type); +} + +void NetworkChangeNotifierDelegateAndroid::FakeNetworkSoonToBeDisconnected( + NetworkChangeNotifier::NetworkHandle network) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_fakeNetworkSoonToBeDisconnected(env, network); +} + +void NetworkChangeNotifierDelegateAndroid::FakeNetworkDisconnected( + NetworkChangeNotifier::NetworkHandle network) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_fakeNetworkDisconnected(env, network); +} + +void NetworkChangeNotifierDelegateAndroid::FakeUpdateActiveNetworkList( + NetworkChangeNotifier::NetworkList networks) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_fakeUpdateActiveNetworkList( + env, base::android::ToJavaIntArray(env, networks).obj()); +} + +void NetworkChangeNotifierDelegateAndroid::FakeDefaultNetwork( + NetworkChangeNotifier::NetworkHandle network, + ConnectionType type) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_fakeDefaultNetwork(env, network, type); +} + } // namespace net diff --git a/net/android/network_change_notifier_delegate_android.h b/net/android/network_change_notifier_delegate_android.h index e1f874a..d6312b7 100644 --- a/net/android/network_change_notifier_delegate_android.h +++ b/net/android/network_change_notifier_delegate_android.h @@ -5,6 +5,8 @@ #ifndef NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_ANDROID_H_ #define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_ANDROID_H_ +#include <string> + #include "base/android/jni_android.h" #include "base/macros.h" #include "base/memory/ref_counted.h" @@ -23,13 +25,15 @@ namespace net { class NET_EXPORT_PRIVATE NetworkChangeNotifierDelegateAndroid { public: typedef NetworkChangeNotifier::ConnectionType ConnectionType; + typedef NetworkChangeNotifier::NetworkHandle NetworkHandle; + typedef NetworkChangeNotifier::NetworkList NetworkList; // Observer interface implemented by NetworkChangeNotifierAndroid which // subscribes to network change notifications fired by the delegate (and // initiated by the Java side). - class Observer { + class Observer : public NetworkChangeNotifier::NetworkObserver { public: - virtual ~Observer() {} + ~Observer() override {} // Updates the current connection type. virtual void OnConnectionTypeChanged() = 0; @@ -42,16 +46,17 @@ class NET_EXPORT_PRIVATE NetworkChangeNotifierDelegateAndroid { NetworkChangeNotifierDelegateAndroid(); ~NetworkChangeNotifierDelegateAndroid(); - // Called from NetworkChangeNotifierAndroid.java on the JNI thread whenever + // Called from NetworkChangeNotifier.java on the JNI thread whenever // the connection type changes. This updates the current connection type seen // by this class and forwards the notification to the observers that // subscribed through AddObserver(). void NotifyConnectionTypeChanged(JNIEnv* env, jobject obj, - jint new_connection_type); + jint new_connection_type, + jint default_netid); jint GetConnectionType(JNIEnv* env, jobject obj) const; - // Called from NetworkChangeNotifierAndroid.java on the JNI thread whenever + // Called from NetworkChangeNotifier.java on the JNI thread whenever // the maximum bandwidth of the connection changes. This updates the current // max bandwidth seen by this class and forwards the notification to the // observers that subscribed through AddObserver(). @@ -59,18 +64,39 @@ class NET_EXPORT_PRIVATE NetworkChangeNotifierDelegateAndroid { jobject obj, jdouble new_max_bandwidth); + // Called from NetworkChangeNotifier.java on the JNI thread to push + // down notifications of network connectivity events. These functions in + // turn: + // 1) Update |network_map_| and |default_network_|. + // 2) Push notifications to NetworkChangeNotifier which in turn pushes + // notifications to its NetworkObservers. Note that these functions + // perform valuable transformations on the signals like deduplicating. + // For descriptions of what individual calls mean, see + // NetworkChangeNotifierAutoDetect.Observer functions of the same names. + void NotifyOfNetworkConnect(JNIEnv* env, + jobject obj, + jint net_id, + jint connection_type); + void NotifyOfNetworkSoonToDisconnect(JNIEnv* env, jobject obj, jint net_id); + void NotifyOfNetworkDisconnect(JNIEnv* env, jobject obj, jint net_id); + void NotifyUpdateActiveNetworkList(JNIEnv* env, + jobject obj, + jintArray active_networks); + // These methods can be called on any thread. Note that the provided observer // will be notified on the thread AddObserver() is called on. void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); - // Can be called from any thread. + // These methods are simply implementations of NetworkChangeNotifier APIs of + // the same name. They can be called from any thread. ConnectionType GetCurrentConnectionType() const; - - // Can be called from any thread. void GetCurrentMaxBandwidthAndConnectionType( double* max_bandwidth_mbps, ConnectionType* connection_type) const; + ConnectionType GetNetworkConnectionType(NetworkHandle network) const; + NetworkHandle GetCurrentDefaultNetwork() const; + void GetCurrentlyConnectedNetworks(NetworkList* network_list) const; // Initializes JNI bindings. static bool Register(JNIEnv* env); @@ -78,20 +104,39 @@ class NET_EXPORT_PRIVATE NetworkChangeNotifierDelegateAndroid { private: friend class BaseNetworkChangeNotifierAndroidTest; + // Map of active connected networks and their connection type. + typedef std::map<NetworkHandle, ConnectionType> NetworkMap; + + // Converts a Java int[] into a NetworkMap. Expects int[] to contain + // repeated instances of: NetworkHandle, ConnectionType + static void JavaIntArrayToNetworkMap(JNIEnv* env, + jintArray int_array, + NetworkMap* network_map); + + // Setters that grab appropriate lock. void SetCurrentConnectionType(ConnectionType connection_type); void SetCurrentMaxBandwidth(double max_bandwidth); + void SetCurrentDefaultNetwork(NetworkHandle default_network); + void SetCurrentNetworksAndTypes(NetworkMap network_map); // Methods calling the Java side exposed for testing. void SetOnline(); void SetOffline(); + void FakeNetworkConnected(NetworkHandle network, ConnectionType type); + void FakeNetworkSoonToBeDisconnected(NetworkHandle network); + void FakeNetworkDisconnected(NetworkHandle network); + void FakeUpdateActiveNetworkList(NetworkList networks); + void FakeDefaultNetwork(NetworkHandle network, ConnectionType type); base::ThreadChecker thread_checker_; scoped_refptr<base::ObserverListThreadSafe<Observer>> observers_; - scoped_refptr<base::SingleThreadTaskRunner> jni_task_runner_; base::android::ScopedJavaGlobalRef<jobject> java_network_change_notifier_; + mutable base::Lock connection_lock_; // Protects the state below. ConnectionType connection_type_; double connection_max_bandwidth_; + NetworkHandle default_network_; + NetworkMap network_map_; DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierDelegateAndroid); }; diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc index b86b79c..fb480e9 100644 --- a/net/base/network_change_notifier.cc +++ b/net/base/network_change_notifier.cc @@ -56,6 +56,9 @@ class MockNetworkChangeNotifier : public NetworkChangeNotifier { // static bool NetworkChangeNotifier::test_notifications_only_ = false; +// static +const NetworkChangeNotifier::NetworkHandle + NetworkChangeNotifier::kInvalidNetworkHandle = -1; // The main observer class that records UMAs for network events. class HistogramWatcher @@ -631,6 +634,32 @@ double NetworkChangeNotifier::GetMaxBandwidthForConnectionSubtype( } // static +void NetworkChangeNotifier::GetConnectedNetworks(NetworkList* network_list) { + if (g_network_change_notifier) { + g_network_change_notifier->GetCurrentConnectedNetworks(network_list); + } else { + network_list->clear(); + } +} + +// static +NetworkChangeNotifier::ConnectionType +NetworkChangeNotifier::GetNetworkConnectionType(NetworkHandle network) { + return g_network_change_notifier + ? g_network_change_notifier->GetCurrentNetworkConnectionType( + network) + : CONNECTION_UNKNOWN; +} + +// static +NetworkChangeNotifier::NetworkHandle +NetworkChangeNotifier::GetDefaultNetwork() { + return g_network_change_notifier + ? g_network_change_notifier->GetCurrentDefaultNetwork() + : kInvalidNetworkHandle; +} + +// static void NetworkChangeNotifier::GetDnsConfig(DnsConfig* config) { if (!g_network_change_notifier) { *config = DnsConfig(); @@ -808,6 +837,12 @@ void NetworkChangeNotifier::AddMaxBandwidthObserver( } } +void NetworkChangeNotifier::AddNetworkObserver(NetworkObserver* observer) { + if (g_network_change_notifier) { + g_network_change_notifier->network_observer_list_->AddObserver(observer); + } +} + void NetworkChangeNotifier::RemoveIPAddressObserver( IPAddressObserver* observer) { if (g_network_change_notifier) { @@ -847,6 +882,12 @@ void NetworkChangeNotifier::RemoveMaxBandwidthObserver( } } +void NetworkChangeNotifier::RemoveNetworkObserver(NetworkObserver* observer) { + if (g_network_change_notifier) { + g_network_change_notifier->network_observer_list_->RemoveObserver(observer); + } +} + // static void NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests() { if (g_network_change_notifier) @@ -908,6 +949,8 @@ NetworkChangeNotifier::NetworkChangeNotifier( max_bandwidth_observer_list_(new base::ObserverListThreadSafe< MaxBandwidthObserver>( base::ObserverListBase<MaxBandwidthObserver>::NOTIFY_EXISTING_ONLY)), + network_observer_list_(new base::ObserverListThreadSafe<NetworkObserver>( + base::ObserverListBase<NetworkObserver>::NOTIFY_EXISTING_ONLY)), network_state_(new NetworkState()), network_change_calculator_(new NetworkChangeCalculator(params)) { DCHECK(!g_network_change_notifier); @@ -935,6 +978,22 @@ void NetworkChangeNotifier::GetCurrentMaxBandwidthAndConnectionType( : GetMaxBandwidthForConnectionSubtype(SUBTYPE_UNKNOWN); } +void NetworkChangeNotifier::GetCurrentConnectedNetworks( + NetworkList* network_list) const { + network_list->clear(); +} + +NetworkChangeNotifier::ConnectionType +NetworkChangeNotifier::GetCurrentNetworkConnectionType( + NetworkHandle network) const { + return CONNECTION_UNKNOWN; +} + +NetworkChangeNotifier::NetworkHandle +NetworkChangeNotifier::GetCurrentDefaultNetwork() const { + return kInvalidNetworkHandle; +} + // static void NetworkChangeNotifier::NotifyObserversOfIPAddressChange() { if (g_network_change_notifier && @@ -989,6 +1048,17 @@ void NetworkChangeNotifier::NotifyObserversOfInitialDNSConfigRead() { } // static +void NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChange( + NetworkChangeType type, + NetworkHandle network) { + if (g_network_change_notifier && + !NetworkChangeNotifier::test_notifications_only_) { + g_network_change_notifier->NotifyObserversOfSpecificNetworkChangeImpl( + type, network); + } +} + +// static void NetworkChangeNotifier::SetDnsConfig(const DnsConfig& config) { if (!g_network_change_notifier) return; @@ -1044,6 +1114,29 @@ void NetworkChangeNotifier::NotifyObserversOfMaxBandwidthChangeImpl( max_bandwidth_mbps, type); } +void NetworkChangeNotifier::NotifyObserversOfSpecificNetworkChangeImpl( + NetworkChangeType type, + NetworkHandle network) { + switch (type) { + case CONNECTED: + network_observer_list_->Notify( + FROM_HERE, &NetworkObserver::OnNetworkConnected, network); + break; + case DISCONNECTED: + network_observer_list_->Notify( + FROM_HERE, &NetworkObserver::OnNetworkDisconnected, network); + break; + case SOON_TO_DISCONNECT: + network_observer_list_->Notify( + FROM_HERE, &NetworkObserver::OnNetworkSoonToDisconnect, network); + break; + case MADE_DEFAULT: + network_observer_list_->Notify( + FROM_HERE, &NetworkObserver::OnNetworkMadeDefault, network); + break; + } +} + NetworkChangeNotifier::DisableForTest::DisableForTest() : network_change_notifier_(g_network_change_notifier) { DCHECK(g_network_change_notifier); diff --git a/net/base/network_change_notifier.h b/net/base/network_change_notifier.h index 143abdc..4290331 100644 --- a/net/base/network_change_notifier.h +++ b/net/base/network_change_notifier.h @@ -8,6 +8,7 @@ #include <vector> #include "base/basictypes.h" +#include "base/macros.h" #include "base/observer_list_threadsafe.h" #include "base/time/time.h" #include "net/base/net_export.h" @@ -202,12 +203,49 @@ class NET_EXPORT NetworkChangeNotifier { DISALLOW_COPY_AND_ASSIGN(MaxBandwidthObserver); }; - virtual ~NetworkChangeNotifier(); + // Opaque handle for device-wide connection to a particular network. For + // example an association with a particular WiFi network with a particular + // SSID or a connection to particular cellular network. + // The meaning of this handle is target-dependent. On Android NetworkHandles + // are equivalent to the framework's concept of NetIDs (e.g. Network.netId). + typedef int32_t NetworkHandle; + + // A list of networks. + typedef std::vector<NetworkHandle> NetworkList; + + // An interface that when implemented and added via AddNeworkObserver(), + // provides notifications when networks come and go. + // Only implemented for Android (Lollipop and newer), no callbacks issued when + // unimplemented. + class NET_EXPORT NetworkObserver { + public: + // Called when device connects to |network|. For example device associates + // with a WiFi access point. This does not imply the network has Internet + // access as it may well be behind a captive portal. + virtual void OnNetworkConnected(NetworkHandle network) = 0; + // Called when device disconnects from |network|. + virtual void OnNetworkDisconnected(NetworkHandle network) = 0; + // Called when device determines the connection to |network| is no longer + // preferred, for example when a device transitions from cellular to WiFi + // it might deem the cellular connection no longer preferred. The device + // will disconnect from |network| in a period of time (30s on Android), + // allowing network communications via |network| to wrap up. + virtual void OnNetworkSoonToDisconnect(NetworkHandle network) = 0; + // Called when |network| is made the default network for communication. + virtual void OnNetworkMadeDefault(NetworkHandle network) = 0; - // See the description of NetworkChangeNotifier::GetConnectionType(). - // Implementations must be thread-safe. Implementations must also be - // cheap as it is called often. - virtual ConnectionType GetCurrentConnectionType() const = 0; + protected: + NetworkObserver() {} + virtual ~NetworkObserver() {} + + private: + DISALLOW_COPY_AND_ASSIGN(NetworkObserver); + }; + + // An invalid NetworkHandle. + static const NetworkHandle kInvalidNetworkHandle; + + virtual ~NetworkChangeNotifier(); // Replaces the default class factory instance of NetworkChangeNotifier class. // The method will take over the ownership of |factory| object. @@ -253,6 +291,27 @@ class NET_EXPORT NetworkChangeNotifier { // TODO(jkarlin): Rename to GetMaxBandwidthMbpsForConnectionSubtype. static double GetMaxBandwidthForConnectionSubtype(ConnectionSubtype subtype); + // Sets |network_list| to a list of all networks that are currently connected. + // Only implemented for Android (Lollipop and newer), leaves |network_list| + // empty when unimplemented. + static void GetConnectedNetworks(NetworkList* network_list); + + // Returns the type of connection |network| uses. Note that this may vary + // slightly over time (e.g. CONNECTION_2G to CONNECTION_3G). If |network| + // is no longer connected, it will return CONNECTION_UNKNOWN. + // Only implemented for Android (Lollipop and newer), returns + // CONNECTION_UNKNOWN when unimplemented. + static ConnectionType GetNetworkConnectionType(NetworkHandle network); + + // Returns the device's current default network connection. This is the + // network used for newly created socket communication for sockets that are + // not explicitly bound to a particular network (e.g. via + // DatagramClientSocket.BindToNetwork). Returns |kInvalidNetworkHandle| if + // there is no default connected network. + // Only implemented for Android (Lollipop and newer), returns + // |kInvalidNetworkHandle| when unimplemented. + static NetworkHandle GetDefaultNetwork(); + // Retrieve the last read DnsConfig. This could be expensive if the system has // a large HOSTS file. static void GetDnsConfig(DnsConfig* config); @@ -300,6 +359,7 @@ class NET_EXPORT NetworkChangeNotifier { static void AddDNSObserver(DNSObserver* observer); static void AddNetworkChangeObserver(NetworkChangeObserver* observer); static void AddMaxBandwidthObserver(MaxBandwidthObserver* observer); + static void AddNetworkObserver(NetworkObserver* observer); // Unregisters |observer| from receiving notifications. This must be called // on the same thread on which AddObserver() was called. Like AddObserver(), @@ -313,6 +373,7 @@ class NET_EXPORT NetworkChangeNotifier { static void RemoveDNSObserver(DNSObserver* observer); static void RemoveNetworkChangeObserver(NetworkChangeObserver* observer); static void RemoveMaxBandwidthObserver(MaxBandwidthObserver* observer); + static void RemoveNetworkObserver(NetworkObserver* observer); // Allow unit tests to trigger notifications. static void NotifyObserversOfIPAddressChangeForTests(); @@ -370,6 +431,15 @@ class NET_EXPORT NetworkChangeNotifier { }; protected: + // Types of network changes specified to + // NotifyObserversOfSpecificNetworkChange. + enum NetworkChangeType { + CONNECTED, + DISCONNECTED, + SOON_TO_DISCONNECT, + MADE_DEFAULT + }; + // NetworkChanged signal is calculated from the IPAddressChanged and // ConnectionTypeChanged signals. Delay parameters control how long to delay // producing NetworkChanged signal after particular input signals so as to @@ -403,12 +473,18 @@ class NET_EXPORT NetworkChangeNotifier { GetAddressTrackerInternal() const; #endif - // See the description of NetworkChangeNotifier::GetMaxBandwidth(). + // These are the actual implementations of the static queryable APIs. + // See the description of the corresponding functions named without "Current". // Implementations must be thread-safe. Implementations must also be - // cheap as it is called often. + // cheap as they are called often. + virtual ConnectionType GetCurrentConnectionType() const = 0; virtual void GetCurrentMaxBandwidthAndConnectionType( double* max_bandwidth_mbps, ConnectionType* connection_type) const; + virtual void GetCurrentConnectedNetworks(NetworkList* network_list) const; + virtual ConnectionType GetCurrentNetworkConnectionType( + NetworkHandle network) const; + virtual NetworkHandle GetCurrentDefaultNetwork() const; // Broadcasts a notification to all registered observers. Note that this // happens asynchronously, even for observers on the current thread, even in @@ -420,6 +496,8 @@ class NET_EXPORT NetworkChangeNotifier { static void NotifyObserversOfNetworkChange(ConnectionType type); static void NotifyObserversOfMaxBandwidthChange(double max_bandwidth_mbps, ConnectionType type); + static void NotifyObserversOfSpecificNetworkChange(NetworkChangeType type, + NetworkHandle network); // Stores |config| in NetworkState and notifies OnDNSChanged observers. static void SetDnsConfig(const DnsConfig& config); @@ -443,6 +521,8 @@ class NET_EXPORT NetworkChangeNotifier { void NotifyObserversOfNetworkChangeImpl(ConnectionType type); void NotifyObserversOfMaxBandwidthChangeImpl(double max_bandwidth_mbps, ConnectionType type); + void NotifyObserversOfSpecificNetworkChangeImpl(NetworkChangeType type, + NetworkHandle network); const scoped_refptr<base::ObserverListThreadSafe<IPAddressObserver>> ip_address_observer_list_; @@ -454,6 +534,8 @@ class NET_EXPORT NetworkChangeNotifier { network_change_observer_list_; const scoped_refptr<base::ObserverListThreadSafe<MaxBandwidthObserver>> max_bandwidth_observer_list_; + const scoped_refptr<base::ObserverListThreadSafe<NetworkObserver>> + network_observer_list_; // The current network state. Hosts DnsConfig, exposed via GetDnsConfig. scoped_ptr<NetworkState> network_state_; diff --git a/net/dns/address_sorter_posix_unittest.cc b/net/dns/address_sorter_posix_unittest.cc index 88a135f..ab63ac4 100644 --- a/net/dns/address_sorter_posix_unittest.cc +++ b/net/dns/address_sorter_posix_unittest.cc @@ -57,6 +57,10 @@ class TestUDPClientSocket : public DatagramClientSocket { *address = local_endpoint_; return OK; } + int BindToNetwork(NetworkChangeNotifier::NetworkHandle network) override { + NOTIMPLEMENTED(); + return OK; + } int Connect(const IPEndPoint& remote) override { if (connected_) diff --git a/net/net.gyp b/net/net.gyp index 2b2b343..6c7217b 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -1389,6 +1389,7 @@ 'cert_verify_status_android_java', 'certificate_mime_types_java', 'network_change_notifier_types_java', + 'network_change_notifier_android_types_java', 'net_errors_java', 'private_key_types_java', 'remote_android_keystore_aidl', @@ -1485,6 +1486,14 @@ 'includes': [ '../build/android/java_cpp_enum.gypi' ], }, { + 'target_name': 'network_change_notifier_android_types_java', + 'type': 'none', + 'variables': { + 'source_file': 'android/network_change_notifier_android.cc', + }, + 'includes': [ '../build/android/java_cpp_enum.gypi' ], + }, + { 'target_name': 'private_key_types_java', 'type': 'none', 'variables': { diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc index 1436b14..19a6722 100644 --- a/net/socket/socket_test_util.cc +++ b/net/socket/socket_test_util.cc @@ -1285,6 +1285,11 @@ int DeterministicMockUDPClientSocket::CompleteRead() { return helper_.CompleteRead(); } +int DeterministicMockUDPClientSocket::BindToNetwork( + NetworkChangeNotifier::NetworkHandle network) { + return ERR_NOT_IMPLEMENTED; +} + int DeterministicMockUDPClientSocket::Connect(const IPEndPoint& address) { if (connected_) return OK; @@ -1651,6 +1656,11 @@ const BoundNetLog& MockUDPClientSocket::NetLog() const { return net_log_; } +int MockUDPClientSocket::BindToNetwork( + NetworkChangeNotifier::NetworkHandle network) { + return ERR_NOT_IMPLEMENTED; +} + int MockUDPClientSocket::Connect(const IPEndPoint& address) { connected_ = true; peer_addr_ = address; diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h index 6b966df..b576df1 100644 --- a/net/socket/socket_test_util.h +++ b/net/socket/socket_test_util.h @@ -823,6 +823,7 @@ class DeterministicMockUDPClientSocket const BoundNetLog& NetLog() const override; // DatagramClientSocket implementation. + int BindToNetwork(NetworkChangeNotifier::NetworkHandle network) override; int Connect(const IPEndPoint& address) override; void set_source_port(uint16 port) { source_port_ = port; } @@ -945,6 +946,7 @@ class MockUDPClientSocket : public DatagramClientSocket, public AsyncSocket { const BoundNetLog& NetLog() const override; // DatagramClientSocket implementation. + int BindToNetwork(NetworkChangeNotifier::NetworkHandle network) override; int Connect(const IPEndPoint& address) override; // AsyncSocket implementation. diff --git a/net/tools/net_watcher/net_watcher.cc b/net/tools/net_watcher/net_watcher.cc index ca25040..1b22804 100644 --- a/net/tools/net_watcher/net_watcher.cc +++ b/net/tools/net_watcher/net_watcher.cc @@ -199,7 +199,7 @@ int main(int argc, char* argv[]) { LOG(INFO) << "Initial connection type: " << ConnectionTypeToString( - network_change_notifier->GetCurrentConnectionType()); + net::NetworkChangeNotifier::GetConnectionType()); { net::ProxyConfig config; diff --git a/net/udp/datagram_client_socket.h b/net/udp/datagram_client_socket.h index c2c2cba..ac4632e 100644 --- a/net/udp/datagram_client_socket.h +++ b/net/udp/datagram_client_socket.h @@ -5,6 +5,7 @@ #ifndef NET_UDP_DATAGRAM_CLIENT_SOCKET_H_ #define NET_UDP_DATAGRAM_CLIENT_SOCKET_H_ +#include "net/base/network_change_notifier.h" #include "net/socket/socket.h" #include "net/udp/datagram_socket.h" @@ -17,6 +18,13 @@ class NET_EXPORT_PRIVATE DatagramClientSocket : public DatagramSocket, public: ~DatagramClientSocket() override {} + // Binds this socket to |network|. All data traffic on the socket will be sent + // and received via |network|. Must be called before Connect(). This call will + // fail if |network| has disconnected. Communication using this socket will + // fail if |network| disconnects. + // Returns a net error code. + virtual int BindToNetwork(NetworkChangeNotifier::NetworkHandle network) = 0; + // Initialize this socket as a client socket to server at |address|. // Returns a network error code. virtual int Connect(const IPEndPoint& address) = 0; diff --git a/net/udp/udp_client_socket.cc b/net/udp/udp_client_socket.cc index 5b7639f..ca1f9e2 100644 --- a/net/udp/udp_client_socket.cc +++ b/net/udp/udp_client_socket.cc @@ -19,6 +19,11 @@ UDPClientSocket::UDPClientSocket(DatagramSocket::BindType bind_type, UDPClientSocket::~UDPClientSocket() { } +int UDPClientSocket::BindToNetwork( + NetworkChangeNotifier::NetworkHandle network) { + return socket_.BindToNetwork(network); +} + int UDPClientSocket::Connect(const IPEndPoint& address) { int rv = socket_.Open(address.GetFamily()); if (rv != OK) diff --git a/net/udp/udp_client_socket.h b/net/udp/udp_client_socket.h index 50a1b29..60fa720 100644 --- a/net/udp/udp_client_socket.h +++ b/net/udp/udp_client_socket.h @@ -24,6 +24,7 @@ class NET_EXPORT_PRIVATE UDPClientSocket : public DatagramClientSocket { ~UDPClientSocket() override; // DatagramClientSocket implementation. + int BindToNetwork(NetworkChangeNotifier::NetworkHandle network) override; int Connect(const IPEndPoint& address) override; int Read(IOBuffer* buf, int buf_len, diff --git a/net/udp/udp_socket_posix.cc b/net/udp/udp_socket_posix.cc index 3b60bf2..f747798 100644 --- a/net/udp/udp_socket_posix.cc +++ b/net/udp/udp_socket_posix.cc @@ -28,6 +28,11 @@ #include "net/socket/socket_descriptor.h" #include "net/udp/udp_net_log_parameters.h" +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#include "base/native_library.h" +#include "base/strings/utf_string_conversions.h" +#endif namespace net { @@ -319,6 +324,42 @@ int UDPSocketPosix::Bind(const IPEndPoint& address) { return rv; } +int UDPSocketPosix::BindToNetwork( + NetworkChangeNotifier::NetworkHandle network) { +#if defined(OS_ANDROID) + DCHECK_NE(socket_, kInvalidSocket); + DCHECK(CalledOnValidThread()); + DCHECK(!is_connected()); + // Android prior to Lollipop didn't have support for binding sockets to + // networks. + if (base::android::BuildInfo::GetInstance()->sdk_int() < + base::android::SDK_VERSION_LOLLIPOP) { + return ERR_NOT_IMPLEMENTED; + } + // NOTE(pauljensen): This does rely on Android implementation details, but + // these details are unlikely to change. + typedef int (*SetNetworkForSocket)(unsigned netId, int socketFd); + static SetNetworkForSocket setNetworkForSocket; + // This is racy, but all racers should come out with the same answer so it + // shouldn't matter. + if (setNetworkForSocket == nullptr) { + // Android's netd client library should always be loaded in our address + // space as it shims libc functions like connect(). + base::FilePath file(base::FilePath::FromUTF16Unsafe( + base::GetNativeLibraryName(base::ASCIIToUTF16("netd_client")))); + base::NativeLibrary lib = base::LoadNativeLibrary(file, nullptr); + setNetworkForSocket = reinterpret_cast<SetNetworkForSocket>( + base::GetFunctionPointerFromNativeLibrary(lib, "setNetworkForSocket")); + } + if (setNetworkForSocket == nullptr) + return ERR_NOT_IMPLEMENTED; + return MapSystemError(setNetworkForSocket(network, socket_)); +#else + NOTIMPLEMENTED(); + return ERR_NOT_IMPLEMENTED; +#endif +} + int UDPSocketPosix::SetReceiveBufferSize(int32 size) { DCHECK_NE(socket_, kInvalidSocket); DCHECK(CalledOnValidThread()); diff --git a/net/udp/udp_socket_posix.h b/net/udp/udp_socket_posix.h index e27c2f4..eb18daa 100644 --- a/net/udp/udp_socket_posix.h +++ b/net/udp/udp_socket_posix.h @@ -34,6 +34,13 @@ class NET_EXPORT UDPSocketPosix : public base::NonThreadSafe { // Returns a net error code. int Open(AddressFamily address_family); + // Binds this socket to |network|. All data traffic on the socket will be sent + // and received via |network|. Must be called before Connect(). This call will + // fail if |network| has disconnected. Communication using this socket will + // fail if |network| disconnects. + // Returns a net error code. + int BindToNetwork(NetworkChangeNotifier::NetworkHandle network); + // Connects the socket to connect with a certain |address|. // Should be called after Open(). // Returns a net error code. diff --git a/net/udp/udp_socket_win.cc b/net/udp/udp_socket_win.cc index f9c1d20..c7ba423 100644 --- a/net/udp/udp_socket_win.cc +++ b/net/udp/udp_socket_win.cc @@ -482,6 +482,11 @@ int UDPSocketWin::Bind(const IPEndPoint& address) { return rv; } +int UDPSocketWin::BindToNetwork(NetworkChangeNotifier::NetworkHandle network) { + NOTIMPLEMENTED(); + return ERR_NOT_IMPLEMENTED; +} + int UDPSocketWin::SetReceiveBufferSize(int32 size) { DCHECK_NE(socket_, INVALID_SOCKET); DCHECK(CalledOnValidThread()); diff --git a/net/udp/udp_socket_win.h b/net/udp/udp_socket_win.h index 24e1bad..d01e67b 100644 --- a/net/udp/udp_socket_win.h +++ b/net/udp/udp_socket_win.h @@ -40,6 +40,13 @@ class NET_EXPORT UDPSocketWin // Returns a net error code. int Open(AddressFamily address_family); + // Binds this socket to |network|. All data traffic on the socket will be sent + // and received via |network|. Must be called before Connect(). This call will + // fail if |network| has disconnected. Communication using this socket will + // fail if |network| disconnects. + // Returns a net error code. + int BindToNetwork(NetworkChangeNotifier::NetworkHandle network); + // Connects the socket to connect with a certain |address|. // Should be called after Open(). // Returns a net error code. |