diff options
Diffstat (limited to 'net')
23 files changed, 1488 insertions, 52 deletions
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. |