Create network change notifier and pass the event to NetworkManager
BUG=
Review URL: https://codereview.webrtc.org/1391703003
Cr-Commit-Position: refs/heads/master@{#10325}
diff --git a/talk/app/webrtc/androidtests/AndroidManifest.xml b/talk/app/webrtc/androidtests/AndroidManifest.xml
index ef6beb8..75b6d61 100644
--- a/talk/app/webrtc/androidtests/AndroidManifest.xml
+++ b/talk/app/webrtc/androidtests/AndroidManifest.xml
@@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
diff --git a/talk/app/webrtc/androidtests/src/org/webrtc/NetworkMonitorTest.java b/talk/app/webrtc/androidtests/src/org/webrtc/NetworkMonitorTest.java
new file mode 100644
index 0000000..98a2363
--- /dev/null
+++ b/talk/app/webrtc/androidtests/src/org/webrtc/NetworkMonitorTest.java
@@ -0,0 +1,288 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.webrtc;
+
+import static org.webrtc.NetworkMonitorAutoDetect.ConnectionType;
+import static org.webrtc.NetworkMonitorAutoDetect.ConnectivityManagerDelegate;
+import static org.webrtc.NetworkMonitorAutoDetect.INVALID_NET_ID;
+import static org.webrtc.NetworkMonitorAutoDetect.NetworkState;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.TelephonyManager;
+import android.test.ActivityTestCase;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests for org.webrtc.NetworkMonitor.
+ */
+@SuppressLint("NewApi")
+public class NetworkMonitorTest extends ActivityTestCase {
+ /**
+ * Listens for alerts fired by the NetworkMonitor when network status changes.
+ */
+ private static class NetworkMonitorTestObserver
+ implements NetworkMonitor.NetworkObserver {
+ private boolean receivedNotification = false;
+
+ @Override
+ public void onConnectionTypeChanged(ConnectionType connectionType) {
+ receivedNotification = true;
+ }
+
+ public boolean hasReceivedNotification() {
+ return receivedNotification;
+ }
+
+ public void resetHasReceivedNotification() {
+ receivedNotification = false;
+ }
+ }
+
+ /**
+ * Mocks out calls to the ConnectivityManager.
+ */
+ private static class MockConnectivityManagerDelegate extends ConnectivityManagerDelegate {
+ private boolean activeNetworkExists;
+ private int networkType;
+ private int networkSubtype;
+
+ @Override
+ public NetworkState getNetworkState() {
+ return new NetworkState(activeNetworkExists, networkType, networkSubtype);
+ }
+
+ // Dummy implementations to avoid NullPointerExceptions in default implementations:
+
+ @Override
+ public int getDefaultNetId() {
+ return INVALID_NET_ID;
+ }
+
+ @Override
+ public Network[] getAllNetworks() {
+ return new Network[0];
+ }
+
+ @Override
+ public NetworkState getNetworkState(Network network) {
+ return new NetworkState(false, -1, -1);
+ }
+
+ public void setActiveNetworkExists(boolean networkExists) {
+ activeNetworkExists = networkExists;
+ }
+
+ public void setNetworkType(int networkType) {
+ this.networkType = networkType;
+ }
+
+ public void setNetworkSubtype(int networkSubtype) {
+ this.networkSubtype = networkSubtype;
+ }
+ }
+
+ /**
+ * Mocks out calls to the WifiManager.
+ */
+ private static class MockWifiManagerDelegate
+ extends NetworkMonitorAutoDetect.WifiManagerDelegate {
+ private String wifiSSID;
+
+ @Override
+ public String getWifiSSID() {
+ return wifiSSID;
+ }
+
+ public void setWifiSSID(String wifiSSID) {
+ this.wifiSSID = wifiSSID;
+ }
+ }
+
+ // A dummy NetworkMonitorAutoDetect.Observer.
+ private static class TestNetworkMonitorAutoDetectObserver
+ implements NetworkMonitorAutoDetect.Observer {
+
+ @Override
+ public void onConnectionTypeChanged(ConnectionType newConnectionType) {}
+ }
+
+ private static final Object lock = new Object();
+ private static Handler uiThreadHandler = null;
+
+ private NetworkMonitorAutoDetect receiver;
+ private MockConnectivityManagerDelegate connectivityDelegate;
+ private MockWifiManagerDelegate wifiDelegate;
+
+ private static Handler getUiThreadHandler() {
+ synchronized (lock) {
+ if (uiThreadHandler == null ) {
+ uiThreadHandler = new Handler(Looper.getMainLooper());
+ }
+ return uiThreadHandler;
+ }
+ }
+
+ /**
+ * Helper method to create a network monitor and delegates for testing.
+ */
+ private void createTestMonitor() {
+ Context context = getInstrumentation().getTargetContext();
+ NetworkMonitor.resetInstanceForTests(context);
+ NetworkMonitor.setAutoDetectConnectivityState(true);
+ receiver = NetworkMonitor.getAutoDetectorForTest();
+ assertNotNull(receiver);
+
+ connectivityDelegate = new MockConnectivityManagerDelegate();
+ connectivityDelegate.setActiveNetworkExists(true);
+ receiver.setConnectivityManagerDelegateForTests(connectivityDelegate);
+
+ wifiDelegate = new MockWifiManagerDelegate();
+ receiver.setWifiManagerDelegateForTests(wifiDelegate);
+ wifiDelegate.setWifiSSID("foo");
+ }
+
+ private NetworkMonitorAutoDetect.ConnectionType getCurrentConnectionType() {
+ final NetworkMonitorAutoDetect.NetworkState networkState =
+ receiver.getCurrentNetworkState();
+ return receiver.getCurrentConnectionType(networkState);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ getUiThreadHandler().post(new Runnable() {
+ public void run() {
+ createTestMonitor();
+ }
+ });
+ }
+
+ /**
+ * Tests that the receiver registers for connectivity intents during construction.
+ */
+ @UiThreadTest
+ @SmallTest
+ public void testNetworkMonitorRegistersInConstructor() throws InterruptedException {
+ Context context = getInstrumentation().getTargetContext();
+
+ NetworkMonitorAutoDetect.Observer observer = new TestNetworkMonitorAutoDetectObserver();
+
+ NetworkMonitorAutoDetect receiver = new NetworkMonitorAutoDetect(observer, context);
+
+ assertTrue(receiver.isReceiverRegisteredForTesting());
+ }
+
+ /**
+ * Tests that when there is an intent indicating a change in network connectivity, it sends a
+ * notification to Java observers.
+ */
+ @UiThreadTest
+ @MediumTest
+ public void testNetworkMonitorJavaObservers() throws InterruptedException {
+ // Initialize the NetworkMonitor with a connection.
+ Intent connectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+
+ // We shouldn't be re-notified if the connection hasn't actually changed.
+ NetworkMonitorTestObserver observer = new NetworkMonitorTestObserver();
+ NetworkMonitor.addNetworkObserver(observer);
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+ assertFalse(observer.hasReceivedNotification());
+
+ // We shouldn't be notified if we're connected to non-Wifi and the Wifi SSID changes.
+ wifiDelegate.setWifiSSID("bar");
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+ assertFalse(observer.hasReceivedNotification());
+
+ // We should be notified when we change to Wifi.
+ connectivityDelegate.setNetworkType(ConnectivityManager.TYPE_WIFI);
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+ assertTrue(observer.hasReceivedNotification());
+ observer.resetHasReceivedNotification();
+
+ // We should be notified when the Wifi SSID changes.
+ wifiDelegate.setWifiSSID("foo");
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+ assertTrue(observer.hasReceivedNotification());
+ observer.resetHasReceivedNotification();
+
+ // We shouldn't be re-notified if the Wifi SSID hasn't actually changed.
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+ assertFalse(observer.hasReceivedNotification());
+
+ // Mimic that connectivity has been lost and ensure that the observer gets the notification.
+ connectivityDelegate.setActiveNetworkExists(false);
+ Intent noConnectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+ receiver.onReceive(getInstrumentation().getTargetContext(), noConnectivityIntent);
+ 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
+ @SmallTest
+ 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();
+ }
+ }
+
+ /**
+ * Tests that NetworkMonitorAutoDetect 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
+ @SmallTest
+ public void testQueryableAPIsDoNotCrash() {
+ NetworkMonitorAutoDetect.Observer observer = new TestNetworkMonitorAutoDetectObserver();
+ NetworkMonitorAutoDetect ncn =
+ new NetworkMonitorAutoDetect(observer, getInstrumentation().getTargetContext());
+ ncn.getDefaultNetId();
+ }
+}
diff --git a/talk/app/webrtc/java/android/org/webrtc/NetworkMonitor.java b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitor.java
new file mode 100644
index 0000000..581a223
--- /dev/null
+++ b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitor.java
@@ -0,0 +1,228 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+import static org.webrtc.NetworkMonitorAutoDetect.ConnectionType;
+import static org.webrtc.NetworkMonitorAutoDetect.INVALID_NET_ID;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Borrowed from Chromium's src/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
+ *
+ * Triggers updates to the underlying network state from OS networking events.
+ *
+ * WARNING: This class is not thread-safe.
+ */
+public class NetworkMonitor {
+ /**
+ * Alerted when the connection type of the network changes.
+ * The alert is fired on the UI thread.
+ */
+ public interface NetworkObserver {
+ public void onConnectionTypeChanged(ConnectionType connectionType);
+ }
+
+ private static final String TAG = "NetworkMonitor";
+ private static NetworkMonitor instance;
+
+ private final Context applicationContext;
+
+ // Native observers of the connection type changes.
+ private final ArrayList<Long> nativeNetworkObservers;
+ // Java observers of the connection type changes.
+ private final ArrayList<NetworkObserver> networkObservers;
+
+ // Object that detects the connection type changes.
+ private NetworkMonitorAutoDetect autoDetector;
+
+ private ConnectionType currentConnectionType = ConnectionType.CONNECTION_UNKNOWN;
+
+ private NetworkMonitor(Context context) {
+ assertIsTrue(context != null);
+ applicationContext =
+ context.getApplicationContext() == null ? context : context.getApplicationContext();
+
+ nativeNetworkObservers = new ArrayList<Long>();
+ networkObservers = new ArrayList<NetworkObserver>();
+ }
+
+ /**
+ * Initializes the singleton once.
+ * Called from the native code.
+ */
+ public static NetworkMonitor init(Context context) {
+ if (!isInitialized()) {
+ instance = new NetworkMonitor(context);
+ }
+ return instance;
+ }
+
+ public static boolean isInitialized() {
+ return instance != null;
+ }
+
+ /**
+ * Returns the singleton instance.
+ */
+ public static NetworkMonitor getInstance() {
+ return instance;
+ }
+
+ /**
+ * Enables auto detection of the current network state based on notifications from the system.
+ * Note that passing true here requires the embedding app have the platform ACCESS_NETWORK_STATE
+ * permission.
+ *
+ * @param shouldAutoDetect true if the NetworkMonitor should listen for system changes in
+ * network connectivity.
+ */
+ public static void setAutoDetectConnectivityState(boolean shouldAutoDetect) {
+ getInstance().setAutoDetectConnectivityStateInternal(shouldAutoDetect);
+ }
+
+ private static void assertIsTrue(boolean condition) {
+ if (!condition) {
+ throw new AssertionError("Expected to be true");
+ }
+ }
+
+ // Called by the native code.
+ private void startMonitoring(long nativeObserver) {
+ Log.d(TAG, "Start monitoring from native observer " + nativeObserver);
+ nativeNetworkObservers.add(nativeObserver);
+ setAutoDetectConnectivityStateInternal(true);
+ }
+
+ // Called by the native code.
+ private void stopMonitoring(long nativeObserver) {
+ Log.d(TAG, "Stop monitoring from native observer " + nativeObserver);
+ setAutoDetectConnectivityStateInternal(false);
+ nativeNetworkObservers.remove(nativeObserver);
+ }
+
+ private ConnectionType getCurrentConnectionType() {
+ return currentConnectionType;
+ }
+
+ private int getCurrentDefaultNetId() {
+ return autoDetector == null ? INVALID_NET_ID : autoDetector.getDefaultNetId();
+ }
+
+ private void destroyAutoDetector() {
+ if (autoDetector != null) {
+ autoDetector.destroy();
+ autoDetector = null;
+ }
+ }
+
+ private void setAutoDetectConnectivityStateInternal(boolean shouldAutoDetect) {
+ if (!shouldAutoDetect) {
+ destroyAutoDetector();
+ return;
+ }
+ if (autoDetector == null) {
+ autoDetector = new NetworkMonitorAutoDetect(
+ new NetworkMonitorAutoDetect.Observer() {
+ @Override
+ public void onConnectionTypeChanged(ConnectionType newConnectionType) {
+ updateCurrentConnectionType(newConnectionType);
+ }
+ },
+ applicationContext);
+ final NetworkMonitorAutoDetect.NetworkState networkState =
+ autoDetector.getCurrentNetworkState();
+ updateCurrentConnectionType(autoDetector.getCurrentConnectionType(networkState));
+ }
+ }
+
+ private void updateCurrentConnectionType(ConnectionType newConnectionType) {
+ currentConnectionType = newConnectionType;
+ notifyObserversOfConnectionTypeChange(newConnectionType);
+ }
+
+ /**
+ * Alerts all observers of a connection change.
+ */
+ private void notifyObserversOfConnectionTypeChange(ConnectionType newConnectionType) {
+ for (long nativeObserver : nativeNetworkObservers) {
+ nativeNotifyConnectionTypeChanged(nativeObserver);
+ }
+ for (NetworkObserver observer : networkObservers) {
+ observer.onConnectionTypeChanged(newConnectionType);
+ }
+ }
+
+ /**
+ * Adds an observer for any connection type changes.
+ */
+ public static void addNetworkObserver(NetworkObserver observer) {
+ getInstance().addNetworkObserverInternal(observer);
+ }
+
+ private void addNetworkObserverInternal(NetworkObserver observer) {
+ networkObservers.add(observer);
+ }
+
+ /**
+ * Removes an observer for any connection type changes.
+ */
+ public static void removeNetworkObserver(NetworkObserver observer) {
+ getInstance().removeNetworkObserverInternal(observer);
+ }
+
+ private void removeNetworkObserverInternal(NetworkObserver observer) {
+ networkObservers.remove(observer);
+ }
+
+ /**
+ * Checks if there currently is connectivity.
+ */
+ public static boolean isOnline() {
+ ConnectionType connectionType = getInstance().getCurrentConnectionType();
+ return connectionType != ConnectionType.CONNECTION_UNKNOWN
+ && connectionType != ConnectionType.CONNECTION_NONE;
+ }
+
+ private native long nativeCreateNetworkMonitor();
+
+ private native void nativeNotifyConnectionTypeChanged(long nativePtr);
+
+ // For testing only.
+ static void resetInstanceForTests(Context context) {
+ instance = new NetworkMonitor(context);
+ }
+
+ // For testing only.
+ public static NetworkMonitorAutoDetect getAutoDetectorForTest() {
+ return getInstance().autoDetector;
+ }
+}
diff --git a/talk/app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java
new file mode 100644
index 0000000..e3a7850
--- /dev/null
+++ b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java
@@ -0,0 +1,424 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.webrtc;
+
+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.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+/**
+ * Borrowed from Chromium's
+ * src/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
+ *
+ * Used by the NetworkMonitor to listen to platform changes in connectivity.
+ * Note that use of this class requires that the app have the platform
+ * ACCESS_NETWORK_STATE permission.
+ */
+public class NetworkMonitorAutoDetect extends BroadcastReceiver {
+ static enum ConnectionType {
+ CONNECTION_UNKNOWN,
+ CONNECTION_ETHERNET,
+ CONNECTION_WIFI,
+ CONNECTION_4G,
+ CONNECTION_3G,
+ CONNECTION_2G,
+ CONNECTION_BLUETOOTH,
+ CONNECTION_NONE
+ }
+
+ static class NetworkState {
+ private final boolean connected;
+ // Defined from ConnectivityManager.TYPE_XXX for non-mobile; for mobile, it is
+ // further divided into 2G, 3G, or 4G from the subtype.
+ private final int type;
+ // Defined from NetworkInfo.subtype, which is one of the TelephonyManager.NETWORK_TYPE_XXXs.
+ // Will be useful to find the maximum bandwidth.
+ private final int subtype;
+
+ public NetworkState(boolean connected, int type, int subtype) {
+ this.connected = connected;
+ this.type = type;
+ this.subtype = subtype;
+ }
+
+ public boolean isConnected() {
+ return connected;
+ }
+
+ public int getNetworkType() {
+ return type;
+ }
+
+ public int getNetworkSubType() {
+ return subtype;
+ }
+ }
+
+ /** Queries the ConnectivityManager for information about the current connection. */
+ static class ConnectivityManagerDelegate {
+ private final ConnectivityManager connectivityManager;
+
+ ConnectivityManagerDelegate(Context context) {
+ connectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ // For testing.
+ ConnectivityManagerDelegate() {
+ // All the methods below should be overridden.
+ connectivityManager = null;
+ }
+
+ /**
+ * Returns connection type and status information about the current
+ * default network.
+ */
+ NetworkState getNetworkState() {
+ return getNetworkState(connectivityManager.getActiveNetworkInfo());
+ }
+
+ /**
+ * Returns connection type and status information about |network|.
+ * Only callable on Lollipop and newer releases.
+ */
+ @SuppressLint("NewApi")
+ NetworkState getNetworkState(Network network) {
+ return getNetworkState(connectivityManager.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 connectivityManager.getAllNetworks();
+ }
+
+ /**
+ * Returns the NetID of the current default network. Returns
+ * INVALID_NET_ID if no current default network connected.
+ * Only callable on Lollipop and newer releases.
+ */
+ @SuppressLint("NewApi")
+ 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 = connectivityManager.getActiveNetworkInfo();
+ if (defaultNetworkInfo == null) {
+ return INVALID_NET_ID;
+ }
+ final Network[] networks = getAllNetworks();
+ int defaultNetId = INVALID_NET_ID;
+ for (Network network : networks) {
+ if (!hasInternetCapability(network)) {
+ continue;
+ }
+ final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
+ if (networkInfo != null && networkInfo.getType() == defaultNetworkInfo.getType()) {
+ // There should not be multiple connected networks of the
+ // same type. At least as of Android Marshmallow this is
+ // not supported. If this becomes supported this assertion
+ // may trigger. At that point we could consider using
+ // ConnectivityManager.getDefaultNetwork() though this
+ // may give confusing results with VPNs and is only
+ // available with Android Marshmallow.
+ assert defaultNetId == INVALID_NET_ID;
+ 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 =
+ connectivityManager.getNetworkCapabilities(network);
+ return capabilities != null && capabilities.hasCapability(NET_CAPABILITY_INTERNET);
+ }
+ }
+
+ /** Queries the WifiManager for SSID of the current Wifi connection. */
+ static class WifiManagerDelegate {
+ private final Context context;
+ private final WifiManager wifiManager;
+ private final boolean hasWifiPermission;
+
+ WifiManagerDelegate(Context context) {
+ this.context = context;
+
+ hasWifiPermission = context.getPackageManager().checkPermission(
+ permission.ACCESS_WIFI_STATE, context.getPackageName())
+ == PackageManager.PERMISSION_GRANTED;
+ wifiManager = hasWifiPermission
+ ? (WifiManager) context.getSystemService(Context.WIFI_SERVICE) : null;
+ }
+
+ // For testing.
+ WifiManagerDelegate() {
+ // All the methods below should be overridden.
+ context = null;
+ wifiManager = null;
+ hasWifiPermission = false;
+ }
+
+ String getWifiSSID() {
+ final Intent intent = context.registerReceiver(null,
+ new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION));
+ if (intent != null) {
+ final WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+ if (wifiInfo != null) {
+ final String ssid = wifiInfo.getSSID();
+ if (ssid != null) {
+ return ssid;
+ }
+ }
+ }
+ return "";
+ }
+
+ boolean getHasWifiPermission() {
+ return hasWifiPermission;
+ }
+ }
+
+ static final int INVALID_NET_ID = -1;
+ private static final String TAG = "NetworkMonitorAutoDetect";
+ private static final int UNKNOWN_LINK_SPEED = -1;
+ private final IntentFilter intentFilter;
+
+ // Observer for the connection type change.
+ private final Observer observer;
+
+ private final Context context;
+ // connectivityManagerDelegates and wifiManagerDelegate are only non-final for testing.
+ private ConnectivityManagerDelegate connectivityManagerDelegate;
+ private WifiManagerDelegate wifiManagerDelegate;
+ private boolean isRegistered;
+ private ConnectionType connectionType;
+ private String wifiSSID;
+
+ /**
+ * Observer interface by which observer is notified of network changes.
+ */
+ public static interface Observer {
+ /**
+ * Called when default network changes.
+ */
+ public void onConnectionTypeChanged(ConnectionType newConnectionType);
+ }
+
+ /**
+ * Constructs a NetworkMonitorAutoDetect. Should only be called on UI thread.
+ */
+ public NetworkMonitorAutoDetect(Observer observer, Context context) {
+ this.observer = observer;
+ this.context = context;
+ connectivityManagerDelegate = new ConnectivityManagerDelegate(context);
+ wifiManagerDelegate = new WifiManagerDelegate(context);
+
+ final NetworkState networkState = connectivityManagerDelegate.getNetworkState();
+ connectionType = getCurrentConnectionType(networkState);
+ wifiSSID = getCurrentWifiSSID(networkState);
+ intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+ registerReceiver();
+ }
+
+ /**
+ * Allows overriding the ConnectivityManagerDelegate for tests.
+ */
+ void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) {
+ connectivityManagerDelegate = delegate;
+ }
+
+ /**
+ * Allows overriding the WifiManagerDelegate for tests.
+ */
+ void setWifiManagerDelegateForTests(WifiManagerDelegate delegate) {
+ wifiManagerDelegate = delegate;
+ }
+
+ /**
+ * Returns whether the object has registered to receive network connectivity intents.
+ * Visible for testing.
+ */
+ boolean isReceiverRegisteredForTesting() {
+ return isRegistered;
+ }
+
+ public void destroy() {
+ unregisterReceiver();
+ }
+
+ /**
+ * Registers a BroadcastReceiver in the given context.
+ */
+ private void registerReceiver() {
+ if (!isRegistered) {
+ isRegistered = true;
+ context.registerReceiver(this, intentFilter);
+ }
+ }
+
+ /**
+ * Unregisters the BroadcastReceiver in the given context.
+ */
+ private void unregisterReceiver() {
+ if (isRegistered) {
+ isRegistered = false;
+ context.unregisterReceiver(this);
+ }
+ }
+
+ public NetworkState getCurrentNetworkState() {
+ return connectivityManagerDelegate.getNetworkState();
+ }
+
+ /**
+ * Returns NetID of device's current default connected network used for
+ * communication.
+ * Only implemented on Lollipop and newer releases, returns INVALID_NET_ID
+ * when not implemented.
+ */
+ public int getDefaultNetId() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return INVALID_NET_ID;
+ }
+ return connectivityManagerDelegate.getDefaultNetId();
+ }
+
+ public ConnectionType getCurrentConnectionType(NetworkState networkState) {
+ if (!networkState.isConnected()) {
+ return ConnectionType.CONNECTION_NONE;
+ }
+
+ switch (networkState.getNetworkType()) {
+ case ConnectivityManager.TYPE_ETHERNET:
+ return ConnectionType.CONNECTION_ETHERNET;
+ case ConnectivityManager.TYPE_WIFI:
+ return ConnectionType.CONNECTION_WIFI;
+ case ConnectivityManager.TYPE_WIMAX:
+ return ConnectionType.CONNECTION_4G;
+ case ConnectivityManager.TYPE_BLUETOOTH:
+ return ConnectionType.CONNECTION_BLUETOOTH;
+ case ConnectivityManager.TYPE_MOBILE:
+ // Use information from TelephonyManager to classify the connection.
+ switch (networkState.getNetworkSubType()) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return ConnectionType.CONNECTION_2G;
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return ConnectionType.CONNECTION_3G;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return ConnectionType.CONNECTION_4G;
+ default:
+ return ConnectionType.CONNECTION_UNKNOWN;
+ }
+ default:
+ return ConnectionType.CONNECTION_UNKNOWN;
+ }
+ }
+
+ private String getCurrentWifiSSID(NetworkState networkState) {
+ if (getCurrentConnectionType(networkState) != ConnectionType.CONNECTION_WIFI) return "";
+ return wifiManagerDelegate.getWifiSSID();
+ }
+
+ // BroadcastReceiver
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final NetworkState networkState = getCurrentNetworkState();
+ if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
+ connectionTypeChanged(networkState);
+ }
+ }
+
+ private void connectionTypeChanged(NetworkState networkState) {
+ ConnectionType newConnectionType = getCurrentConnectionType(networkState);
+ String newWifiSSID = getCurrentWifiSSID(networkState);
+ if (newConnectionType == connectionType && newWifiSSID.equals(wifiSSID)) return;
+
+ connectionType = newConnectionType;
+ wifiSSID = newWifiSSID;
+ Log.d(TAG, "Network connectivity changed, type is: " + connectionType);
+ observer.onConnectionTypeChanged(newConnectionType);
+ }
+
+ /**
+ * 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(honghaiz): When we update to Android M SDK, use Network.getNetworkHandle().
+ return Integer.parseInt(network.toString());
+ }
+}
diff --git a/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.cc b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.cc
new file mode 100644
index 0000000..f7a8c07
--- /dev/null
+++ b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.cc
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h"
+
+#include "webrtc/base/common.h"
+#include "talk/app/webrtc/java/jni/classreferenceholder.h"
+#include "talk/app/webrtc/java/jni/jni_helpers.h"
+
+namespace webrtc_jni {
+jobject AndroidNetworkMonitor::application_context_ = nullptr;
+
+// static
+void AndroidNetworkMonitor::SetAndroidContext(JNIEnv* jni, jobject context) {
+ if (application_context_) {
+ jni->DeleteGlobalRef(application_context_);
+ }
+ application_context_ = NewGlobalRef(jni, context);
+}
+
+AndroidNetworkMonitor::AndroidNetworkMonitor()
+ : j_network_monitor_class_(jni(),
+ FindClass(jni(), "org/webrtc/NetworkMonitor")),
+ j_network_monitor_(
+ jni(),
+ jni()->CallStaticObjectMethod(
+ *j_network_monitor_class_,
+ GetStaticMethodID(
+ jni(),
+ *j_network_monitor_class_,
+ "init",
+ "(Landroid/content/Context;)Lorg/webrtc/NetworkMonitor;"),
+ application_context_)) {
+ ASSERT(application_context_ != nullptr);
+ CHECK_EXCEPTION(jni()) << "Error during NetworkMonitor.init";
+}
+
+void AndroidNetworkMonitor::Start() {
+ RTC_CHECK(thread_checker_.CalledOnValidThread());
+ jmethodID m =
+ GetMethodID(jni(), *j_network_monitor_class_, "startMonitoring", "(J)V");
+ jni()->CallVoidMethod(*j_network_monitor_, m, jlongFromPointer(this));
+ CHECK_EXCEPTION(jni()) << "Error during NetworkMonitor.startMonitoring";
+}
+
+void AndroidNetworkMonitor::Stop() {
+ RTC_CHECK(thread_checker_.CalledOnValidThread());
+ jmethodID m =
+ GetMethodID(jni(), *j_network_monitor_class_, "stopMonitoring", "(J)V");
+ jni()->CallVoidMethod(*j_network_monitor_, m, jlongFromPointer(this));
+ CHECK_EXCEPTION(jni()) << "Error during NetworkMonitor.stopMonitoring";
+}
+
+JOW(void, NetworkMonitor_nativeNotifyConnectionTypeChanged)(
+ JNIEnv* jni, jobject j_monitor, jlong j_native_monitor) {
+ rtc::NetworkMonitorInterface* network_monitor =
+ reinterpret_cast<rtc::NetworkMonitorInterface*>(j_native_monitor);
+ network_monitor->OnNetworksChanged();
+}
+
+} // namespace webrtc_jni
diff --git a/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h
new file mode 100644
index 0000000..3f5110c
--- /dev/null
+++ b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h
@@ -0,0 +1,67 @@
+/*
+ * libjingle
+ * Copyright 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_JAVA_JNI_ANDROIDNETWORKMONITOR_JNI_H_
+#define TALK_APP_WEBRTC_JAVA_JNI_ANDROIDNETWORKMONITOR_JNI_H_
+
+#include "webrtc/base/networkmonitor.h"
+
+#include "webrtc/base/thread_checker.h"
+#include "talk/app/webrtc/java/jni/jni_helpers.h"
+
+namespace webrtc_jni {
+
+class AndroidNetworkMonitor : public rtc::NetworkMonitorBase {
+ public:
+ AndroidNetworkMonitor();
+
+ static void SetAndroidContext(JNIEnv* jni, jobject context);
+
+ void Start() override;
+ void Stop() override;
+
+ private:
+ JNIEnv* jni() { return AttachCurrentThreadIfNeeded(); }
+
+ ScopedGlobalRef<jclass> j_network_monitor_class_;
+ ScopedGlobalRef<jobject> j_network_monitor_;
+ rtc::ThreadChecker thread_checker_;
+ static jobject application_context_;
+};
+
+class AndroidNetworkMonitorFactory : public rtc::NetworkMonitorFactory {
+ public:
+ AndroidNetworkMonitorFactory() {}
+
+ rtc::NetworkMonitorInterface* CreateNetworkMonitor() override {
+ return new AndroidNetworkMonitor();
+ }
+};
+
+} // namespace webrtc_jni
+
+#endif // TALK_APP_WEBRTC_JAVA_JNI_ANDROIDNETWORKMONITOR_JNI_H_
diff --git a/talk/app/webrtc/java/jni/classreferenceholder.cc b/talk/app/webrtc/java/jni/classreferenceholder.cc
index d99aa1a..7efea06 100644
--- a/talk/app/webrtc/java/jni/classreferenceholder.cc
+++ b/talk/app/webrtc/java/jni/classreferenceholder.cc
@@ -78,6 +78,7 @@
LoadClass(jni, "org/webrtc/VideoCapturerAndroid");
LoadClass(jni, "org/webrtc/VideoCapturerAndroid$NativeObserver");
LoadClass(jni, "org/webrtc/EglBase");
+ LoadClass(jni, "org/webrtc/NetworkMonitor");
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder");
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo");
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$VideoCodecType");
diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc
index c3ccb8a..1db8621 100644
--- a/talk/app/webrtc/java/jni/peerconnection_jni.cc
+++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc
@@ -76,6 +76,7 @@
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/logsinks.h"
+#include "webrtc/base/networkmonitor.h"
#include "webrtc/base/messagequeue.h"
#include "webrtc/base/ssladapter.h"
#include "webrtc/base/stringutils.h"
@@ -88,6 +89,7 @@
#include "talk/app/webrtc/java/jni/androidmediadecoder_jni.h"
#include "talk/app/webrtc/java/jni/androidmediaencoder_jni.h"
#include "talk/app/webrtc/java/jni/androidvideocapturer_jni.h"
+#include "talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h"
#include "webrtc/modules/video_render/video_render_internal.h"
#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
using webrtc::LogcatTraceContext;
@@ -1023,6 +1025,7 @@
jboolean video_hw_acceleration) {
bool failure = false;
video_hw_acceleration_enabled = video_hw_acceleration;
+ AndroidNetworkMonitor::SetAndroidContext(jni, context);
if (!factory_static_initialized) {
if (initialize_video) {
failure |= webrtc::SetRenderAndroidVM(GetJVM());
@@ -1063,18 +1066,29 @@
Thread* signaling_thread,
WebRtcVideoEncoderFactory* encoder_factory,
WebRtcVideoDecoderFactory* decoder_factory,
+ rtc::NetworkMonitorFactory* network_monitor_factory,
PeerConnectionFactoryInterface* factory)
: worker_thread_(worker_thread),
signaling_thread_(signaling_thread),
encoder_factory_(encoder_factory),
decoder_factory_(decoder_factory),
+ network_monitor_factory_(network_monitor_factory),
factory_(factory) {}
- ~OwnedFactoryAndThreads() { CHECK_RELEASE(factory_); }
+ ~OwnedFactoryAndThreads() {
+ CHECK_RELEASE(factory_);
+ if (network_monitor_factory_ != nullptr) {
+ rtc::NetworkMonitorFactory::ReleaseFactory(network_monitor_factory_);
+ }
+ }
PeerConnectionFactoryInterface* factory() { return factory_; }
WebRtcVideoEncoderFactory* encoder_factory() { return encoder_factory_; }
WebRtcVideoDecoderFactory* decoder_factory() { return decoder_factory_; }
+ rtc::NetworkMonitorFactory* network_monitor_factory() {
+ return network_monitor_factory_;
+ }
+ void clear_network_monitor_factory() { network_monitor_factory_ = nullptr; }
void InvokeJavaCallbacksOnFactoryThreads();
private:
@@ -1084,6 +1098,7 @@
const scoped_ptr<Thread> signaling_thread_;
WebRtcVideoEncoderFactory* encoder_factory_;
WebRtcVideoDecoderFactory* decoder_factory_;
+ rtc::NetworkMonitorFactory* network_monitor_factory_;
PeerConnectionFactoryInterface* factory_; // Const after ctor except dtor.
};
@@ -1132,11 +1147,15 @@
<< "Failed to start threads";
WebRtcVideoEncoderFactory* encoder_factory = nullptr;
WebRtcVideoDecoderFactory* decoder_factory = nullptr;
+ rtc::NetworkMonitorFactory* network_monitor_factory = nullptr;
+
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
if (video_hw_acceleration_enabled) {
encoder_factory = new MediaCodecVideoEncoderFactory();
decoder_factory = new MediaCodecVideoDecoderFactory();
}
+ network_monitor_factory = new AndroidNetworkMonitorFactory();
+ rtc::NetworkMonitorFactory::SetFactory(network_monitor_factory);
#endif
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
webrtc::CreatePeerConnectionFactory(worker_thread,
@@ -1149,7 +1168,7 @@
OwnedFactoryAndThreads* owned_factory = new OwnedFactoryAndThreads(
worker_thread, signaling_thread,
encoder_factory, decoder_factory,
- factory.release());
+ network_monitor_factory, factory.release());
owned_factory->InvokeJavaCallbacksOnFactoryThreads();
return jlongFromPointer(owned_factory);
}
@@ -1247,13 +1266,29 @@
bool disable_encryption =
jni->GetBooleanField(options, disable_encryption_field);
+ jfieldID disable_network_monitor_field =
+ jni->GetFieldID(options_class, "disableNetworkMonitor", "Z");
+ bool disable_network_monitor =
+ jni->GetBooleanField(options, disable_network_monitor_field);
+
PeerConnectionFactoryInterface::Options options_to_set;
// This doesn't necessarily match the c++ version of this struct; feel free
// to add more parameters as necessary.
options_to_set.network_ignore_mask = network_ignore_mask;
options_to_set.disable_encryption = disable_encryption;
+ options_to_set.disable_network_monitor = disable_network_monitor;
factory->SetOptions(options_to_set);
+
+ if (disable_network_monitor) {
+ OwnedFactoryAndThreads* owner =
+ reinterpret_cast<OwnedFactoryAndThreads*>(native_factory);
+ if (owner->network_monitor_factory()) {
+ rtc::NetworkMonitorFactory::ReleaseFactory(
+ owner->network_monitor_factory());
+ owner->clear_network_monitor_factory();
+ }
+ }
}
JOW(void, PeerConnectionFactory_nativeSetVideoHwAccelerationOptions)(
diff --git a/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java b/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java
index 0460624..83999ec 100644
--- a/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java
+++ b/talk/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java
@@ -55,6 +55,7 @@
public int networkIgnoreMask;
public boolean disableEncryption;
+ public boolean disableNetworkMonitor;
}
// |context| is an android.content.Context object, but we keep it untyped here
diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h
index f61f2f6..a5d98ff 100644
--- a/talk/app/webrtc/peerconnectioninterface.h
+++ b/talk/app/webrtc/peerconnectioninterface.h
@@ -541,11 +541,13 @@
Options() :
disable_encryption(false),
disable_sctp_data_channels(false),
+ disable_network_monitor(false),
network_ignore_mask(rtc::kDefaultNetworkIgnoreMask),
ssl_max_version(rtc::SSL_PROTOCOL_DTLS_10) {
}
bool disable_encryption;
bool disable_sctp_data_channels;
+ bool disable_network_monitor;
// Sets the network types to ignore. For instance, calling this with
// ADAPTER_TYPE_ETHERNET | ADAPTER_TYPE_LOOPBACK will ignore Ethernet and
diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp
index fdf0631..81d723a 100755
--- a/talk/libjingle.gyp
+++ b/talk/libjingle.gyp
@@ -104,6 +104,8 @@
'app/webrtc/java/jni/androidmediadecoder_jni.h',
'app/webrtc/java/jni/androidmediaencoder_jni.cc',
'app/webrtc/java/jni/androidmediaencoder_jni.h',
+ 'app/webrtc/java/jni/androidnetworkmonitor_jni.cc',
+ 'app/webrtc/java/jni/androidnetworkmonitor_jni.h',
'app/webrtc/java/jni/surfacetexturehelper_jni.cc',
'app/webrtc/java/jni/surfacetexturehelper_jni.h',
]
@@ -156,6 +158,8 @@
'app/webrtc/java/android/org/webrtc/GlShader.java',
'app/webrtc/java/android/org/webrtc/GlUtil.java',
'app/webrtc/java/android/org/webrtc/GlTextureFrameBuffer.java',
+ 'app/webrtc/java/android/org/webrtc/NetworkMonitor.java',
+ 'app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java',
'app/webrtc/java/android/org/webrtc/RendererCommon.java',
'app/webrtc/java/android/org/webrtc/SurfaceTextureHelper.java',
'app/webrtc/java/android/org/webrtc/SurfaceViewRenderer.java',
diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn
index 1980782..79e204e 100644
--- a/webrtc/base/BUILD.gn
+++ b/webrtc/base/BUILD.gn
@@ -237,6 +237,8 @@
"nethelpers.h",
"network.cc",
"network.h",
+ "networkmonitor.cc",
+ "networkmonitor.h",
"nullsocketserver.h",
"pathutils.cc",
"pathutils.h",
diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp
index 9992d9d..d57fe11 100644
--- a/webrtc/base/base.gyp
+++ b/webrtc/base/base.gyp
@@ -219,6 +219,8 @@
'nethelpers.h',
'network.cc',
'network.h',
+ 'networkmonitor.cc',
+ 'networkmonitor.h',
'nullsocketserver.h',
'openssl.h',
'openssladapter.cc',
diff --git a/webrtc/base/network.cc b/webrtc/base/network.cc
index bc714e3..879c1e4 100644
--- a/webrtc/base/network.cc
+++ b/webrtc/base/network.cc
@@ -48,6 +48,7 @@
#include <algorithm>
#include "webrtc/base/logging.h"
+#include "webrtc/base/networkmonitor.h"
#include "webrtc/base/scoped_ptr.h"
#include "webrtc/base/socket.h" // includes something that makes windows happy
#include "webrtc/base/stream.h"
@@ -329,6 +330,11 @@
BasicNetworkManager::~BasicNetworkManager() {
}
+void BasicNetworkManager::OnNetworksChanged() {
+ LOG(LS_VERBOSE) << "Network change was observed at the network manager";
+ UpdateNetworksOnce();
+}
+
#if defined(__native_client__)
bool BasicNetworkManager::CreateNetworks(bool include_ignored,
@@ -663,6 +669,7 @@
thread_->Post(this, kSignalNetworksMessage);
} else {
thread_->Post(this, kUpdateNetworksMessage);
+ StartNetworkMonitor();
}
++start_count_;
}
@@ -676,13 +683,36 @@
if (!start_count_) {
thread_->Clear(this);
sent_first_update_ = false;
+ StopNetworkMonitor();
}
}
+void BasicNetworkManager::StartNetworkMonitor() {
+ NetworkMonitorFactory* factory = NetworkMonitorFactory::GetFactory();
+ if (factory == nullptr) {
+ return;
+ }
+ network_monitor_.reset(factory->CreateNetworkMonitor());
+ if (!network_monitor_) {
+ return;
+ }
+ network_monitor_->SignalNetworksChanged.connect(
+ this, &BasicNetworkManager::OnNetworksChanged);
+ network_monitor_->Start();
+}
+
+void BasicNetworkManager::StopNetworkMonitor() {
+ if (!network_monitor_) {
+ return;
+ }
+ network_monitor_->Stop();
+ network_monitor_.reset();
+}
+
void BasicNetworkManager::OnMessage(Message* msg) {
switch (msg->message_id) {
- case kUpdateNetworksMessage: {
- DoUpdateNetworks();
+ case kUpdateNetworksMessage: {
+ UpdateNetworksContinually();
break;
}
case kSignalNetworksMessage: {
@@ -694,7 +724,7 @@
}
}
-void BasicNetworkManager::DoUpdateNetworks() {
+void BasicNetworkManager::UpdateNetworksOnce() {
if (!start_count_)
return;
@@ -711,7 +741,10 @@
sent_first_update_ = true;
}
}
+}
+void BasicNetworkManager::UpdateNetworksContinually() {
+ UpdateNetworksOnce();
thread_->PostDelayed(kNetworksUpdateIntervalMs, this, kUpdateNetworksMessage);
}
diff --git a/webrtc/base/network.h b/webrtc/base/network.h
index f365a78..ab3a88d 100644
--- a/webrtc/base/network.h
+++ b/webrtc/base/network.h
@@ -29,6 +29,7 @@
namespace rtc {
class Network;
+class NetworkMonitorInterface;
class Thread;
enum AdapterType {
@@ -147,7 +148,6 @@
private:
friend class NetworkTest;
- void DoUpdateNetworks();
EnumerationPermission enumeration_permission_;
@@ -164,7 +164,8 @@
// Basic implementation of the NetworkManager interface that gets list
// of networks using OS APIs.
class BasicNetworkManager : public NetworkManagerBase,
- public MessageHandler {
+ public MessageHandler,
+ public sigslot::has_slots<> {
public:
BasicNetworkManager();
~BasicNetworkManager() override;
@@ -222,7 +223,17 @@
private:
friend class NetworkTest;
- void DoUpdateNetworks();
+ // Creates a network monitor and listens for network updates.
+ void StartNetworkMonitor();
+ // Stops and removes the network monitor.
+ void StopNetworkMonitor();
+ // Called when it receives updates from the network monitor.
+ void OnNetworksChanged();
+
+ // Updates the networks and reschedules the next update.
+ void UpdateNetworksContinually();
+ // Only updates the networks; does not reschedule the next update.
+ void UpdateNetworksOnce();
Thread* thread_;
bool sent_first_update_;
@@ -230,6 +241,7 @@
std::vector<std::string> network_ignore_list_;
int network_ignore_mask_;
bool ignore_non_default_routes_;
+ scoped_ptr<NetworkMonitorInterface> network_monitor_;
};
// Represents a Unix-type network interface, with a name and single address.
diff --git a/webrtc/base/network_unittest.cc b/webrtc/base/network_unittest.cc
index 6d920e0..4362221 100644
--- a/webrtc/base/network_unittest.cc
+++ b/webrtc/base/network_unittest.cc
@@ -10,6 +10,7 @@
#include "webrtc/base/network.h"
+#include "webrtc/base/networkmonitor.h"
#include <vector>
#if defined(WEBRTC_POSIX)
#include <sys/types.h>
@@ -26,6 +27,20 @@
namespace rtc {
+class FakeNetworkMonitor : public NetworkMonitorBase {
+ public:
+ void Start() override {}
+ void Stop() override {}
+};
+
+class FakeNetworkMonitorFactory : public NetworkMonitorFactory {
+ public:
+ FakeNetworkMonitorFactory() {}
+ NetworkMonitorInterface* CreateNetworkMonitor() {
+ return new FakeNetworkMonitor();
+ }
+};
+
class NetworkTest : public testing::Test, public sigslot::has_slots<> {
public:
NetworkTest() : callback_called_(false) {}
@@ -55,6 +70,18 @@
return list;
}
+ NetworkMonitorInterface* GetNetworkMonitor(
+ BasicNetworkManager& network_manager) {
+ return network_manager.network_monitor_.get();
+ }
+ void ClearNetworks(BasicNetworkManager& network_manager) {
+ for (const auto& kv : network_manager.networks_map_) {
+ delete kv.second;
+ }
+ network_manager.networks_.clear();
+ network_manager.networks_map_.clear();
+ }
+
#if defined(WEBRTC_POSIX)
// Separated from CreateNetworks for tests.
static void CallConvertIfAddrs(const BasicNetworkManager& network_manager,
@@ -790,4 +817,29 @@
EXPECT_EQ(ipv6_network.GetBestIP(), static_cast<IPAddress>(ip));
}
+TEST_F(NetworkTest, TestNetworkMonitoring) {
+ BasicNetworkManager manager;
+ manager.SignalNetworksChanged.connect(static_cast<NetworkTest*>(this),
+ &NetworkTest::OnNetworksChanged);
+ FakeNetworkMonitorFactory* factory = new FakeNetworkMonitorFactory();
+ NetworkMonitorFactory::SetFactory(factory);
+ manager.StartUpdating();
+ NetworkMonitorInterface* network_monitor = GetNetworkMonitor(manager);
+ EXPECT_TRUE_WAIT(callback_called_, 1000);
+ callback_called_ = false;
+
+ // Clear the networks so that there will be network changes below.
+ ClearNetworks(manager);
+ // Network manager is started, so the callback is called when the network
+ // monitor fires the network-change event.
+ network_monitor->OnNetworksChanged();
+ EXPECT_TRUE_WAIT(callback_called_, 1000);
+
+ // Network manager is stopped; the network monitor is removed.
+ manager.StopUpdating();
+ EXPECT_TRUE(GetNetworkMonitor(manager) == nullptr);
+
+ NetworkMonitorFactory::ReleaseFactory(factory);
+}
+
} // namespace rtc
diff --git a/webrtc/base/networkmonitor.cc b/webrtc/base/networkmonitor.cc
new file mode 100644
index 0000000..92bf059
--- /dev/null
+++ b/webrtc/base/networkmonitor.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/base/networkmonitor.h"
+
+#include "webrtc/base/common.h"
+
+namespace {
+const uint32_t UPDATE_NETWORKS_MESSAGE = 1;
+
+// This is set by NetworkMonitorFactory::SetFactory and the caller of
+// NetworkMonitorFactory::SetFactory must be responsible for calling
+// ReleaseFactory to destroy the factory.
+rtc::NetworkMonitorFactory* network_monitor_factory = nullptr;
+} // namespace
+
+namespace rtc {
+NetworkMonitorInterface::NetworkMonitorInterface() {}
+
+NetworkMonitorInterface::~NetworkMonitorInterface() {}
+
+NetworkMonitorBase::NetworkMonitorBase() : thread_(Thread::Current()) {}
+NetworkMonitorBase::~NetworkMonitorBase() {}
+
+void NetworkMonitorBase::OnNetworksChanged() {
+ LOG(LS_VERBOSE) << "Network change is received at the network monitor";
+ thread_->Post(this, UPDATE_NETWORKS_MESSAGE);
+}
+
+void NetworkMonitorBase::OnMessage(Message* msg) {
+ ASSERT(msg->message_id == UPDATE_NETWORKS_MESSAGE);
+ SignalNetworksChanged();
+}
+
+NetworkMonitorFactory::NetworkMonitorFactory() {}
+NetworkMonitorFactory::~NetworkMonitorFactory() {}
+
+void NetworkMonitorFactory::SetFactory(NetworkMonitorFactory* factory) {
+ if (network_monitor_factory != nullptr) {
+ delete network_monitor_factory;
+ }
+ network_monitor_factory = factory;
+}
+
+void NetworkMonitorFactory::ReleaseFactory(NetworkMonitorFactory* factory) {
+ if (factory == network_monitor_factory) {
+ SetFactory(nullptr);
+ }
+}
+
+NetworkMonitorFactory* NetworkMonitorFactory::GetFactory() {
+ return network_monitor_factory;
+}
+
+} // namespace rtc
diff --git a/webrtc/base/networkmonitor.h b/webrtc/base/networkmonitor.h
new file mode 100644
index 0000000..c45c817
--- /dev/null
+++ b/webrtc/base/networkmonitor.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_BASE_NETWORKMONITOR_H_
+#define WEBRTC_BASE_NETWORKMONITOR_H_
+
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/thread.h"
+
+namespace rtc {
+/*
+ * Receives network-change events via |OnNetworksChanged| and signals the
+ * networks changed event.
+ *
+ * Threading consideration:
+ * It is expected that all upstream operations (from native to Java) are
+ * performed from the worker thread. This includes creating, starting and
+ * stopping the monitor. This avoids the potential race condition when creating
+ * the singleton Java NetworkMonitor class. Downstream operations can be from
+ * any thread, but this class will forward all the downstream operations onto
+ * the worker thread.
+ *
+ * Memory consideration:
+ * NetworkMonitor is owned by the caller (NetworkManager). The global network
+ * monitor factory is owned by the factory itself but needs to be released from
+ * the factory creator.
+ */
+// Generic network monitor interface. It starts and stops monitoring network
+// changes, and fires the SignalNetworksChanged event when networks change.
+class NetworkMonitorInterface {
+ public:
+ NetworkMonitorInterface();
+ virtual ~NetworkMonitorInterface();
+
+ sigslot::signal0<> SignalNetworksChanged;
+
+ virtual void Start() = 0;
+ virtual void Stop() = 0;
+
+ // Implementations should call this method on the base when networks change,
+ // and the base will fire SignalNetworksChanged on the right thread.
+ virtual void OnNetworksChanged() = 0;
+};
+
+class NetworkMonitorBase : public NetworkMonitorInterface,
+ public MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ NetworkMonitorBase();
+ ~NetworkMonitorBase() override;
+
+ void OnNetworksChanged() override;
+
+ void OnMessage(Message* msg) override;
+
+ private:
+ Thread* thread_;
+};
+
+/*
+ * NetworkMonitorFactory creates NetworkMonitors.
+ */
+class NetworkMonitorFactory {
+ public:
+ // This is not thread-safe; it should be called once (or once per audio/video
+ // call) during the call initialization.
+ static void SetFactory(NetworkMonitorFactory* factory);
+
+ static void ReleaseFactory(NetworkMonitorFactory* factory);
+ static NetworkMonitorFactory* GetFactory();
+
+ virtual NetworkMonitorInterface* CreateNetworkMonitor() = 0;
+
+ virtual ~NetworkMonitorFactory();
+
+ protected:
+ NetworkMonitorFactory();
+};
+
+} // namespace rtc
+
+#endif // WEBRTC_BASE_NETWORKMONITOR_H_
diff --git a/webrtc/examples/androidapp/AndroidManifest.xml b/webrtc/examples/androidapp/AndroidManifest.xml
index 631660a..6a91cfd 100644
--- a/webrtc/examples/androidapp/AndroidManifest.xml
+++ b/webrtc/examples/androidapp/AndroidManifest.xml
@@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
diff --git a/webrtc/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java b/webrtc/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java
index 4024140..5a5034b 100644
--- a/webrtc/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java
+++ b/webrtc/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java
@@ -236,6 +236,7 @@
PeerConnectionClient client = PeerConnectionClient.getInstance();
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
options.networkIgnoreMask = 0;
+ options.disableNetworkMonitor = true;
client.setPeerConnectionFactoryOptions(options);
client.createPeerConnectionFactory(
getInstrumentation().getContext(), peerConnectionParameters, this);