Add a new interface for creating a udp socket in which it binds the socket to a network if the network handle is set.
Plus, in stunport, turnport and allocation sequence, create a socket using the new interface.

BUG=

Review URL: https://codereview.webrtc.org/1556743002

Cr-Commit-Position: refs/heads/master@{#11279}
diff --git a/talk/app/webrtc/java/android/org/webrtc/NetworkMonitor.java b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitor.java
index 581a223..0f3df42 100644
--- a/talk/app/webrtc/java/android/org/webrtc/NetworkMonitor.java
+++ b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitor.java
@@ -29,9 +29,11 @@
 
 import static org.webrtc.NetworkMonitorAutoDetect.ConnectionType;
 import static org.webrtc.NetworkMonitorAutoDetect.INVALID_NET_ID;
+import static org.webrtc.NetworkMonitorAutoDetect.NetworkInformation;
+
+import org.webrtc.Logging;
 
 import android.content.Context;
-import android.util.Log;
 
 import java.util.ArrayList;
 
@@ -117,14 +119,14 @@
 
   // Called by the native code.
   private void startMonitoring(long nativeObserver) {
-    Log.d(TAG, "Start monitoring from native observer " + nativeObserver);
+    Logging.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);
+    Logging.d(TAG, "Stop monitoring from native observer " + nativeObserver);
     setAutoDetectConnectivityStateInternal(false);
     nativeNetworkObservers.remove(nativeObserver);
   }
@@ -156,11 +158,15 @@
           public void onConnectionTypeChanged(ConnectionType newConnectionType) {
             updateCurrentConnectionType(newConnectionType);
           }
+          @Override
+          public void onNetworkConnect(NetworkInformation networkInfo) {
+            updateNetworkInformation(networkInfo);
+          }
         },
         applicationContext);
       final NetworkMonitorAutoDetect.NetworkState networkState =
           autoDetector.getCurrentNetworkState();
-      updateCurrentConnectionType(autoDetector.getCurrentConnectionType(networkState));
+      updateCurrentConnectionType(autoDetector.getConnectionType(networkState));
     }
   }
 
@@ -181,6 +187,12 @@
     }
   }
 
+  private void updateNetworkInformation(NetworkInformation networkInfo) {
+    for (long nativeObserver : nativeNetworkObservers) {
+      nativeNotifyOfNetworkConnect(nativeObserver, networkInfo);
+    }
+  }
+
   /**
    * Adds an observer for any connection type changes.
    */
@@ -216,6 +228,8 @@
 
   private native void nativeNotifyConnectionTypeChanged(long nativePtr);
 
+  private native void nativeNotifyOfNetworkConnect(long nativePtr, NetworkInformation networkInfo);
+
   // For testing only.
   static void resetInstanceForTests(Context context) {
     instance = new NetworkMonitor(context);
diff --git a/talk/app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java
index 950dcdf..baadd27 100644
--- a/talk/app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java
+++ b/talk/app/webrtc/java/android/org/webrtc/NetworkMonitorAutoDetect.java
@@ -29,6 +29,8 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 
+import org.webrtc.Logging;
+
 import android.Manifest.permission;
 import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
@@ -37,14 +39,17 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkRequest;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.telephony.TelephonyManager;
-import android.util.Log;
 
 /**
  * Borrowed from Chromium's
@@ -66,6 +71,28 @@
     CONNECTION_NONE
   }
 
+  public static class IPAddress {
+    public final byte[] address;
+    public IPAddress (byte[] address) {
+      this.address = address;
+    }
+  }
+
+  /** Java version of NetworkMonitor.NetworkInformation */
+  public static class NetworkInformation{
+    public final String name;
+    public final ConnectionType type;
+    public final int handle;
+    public final IPAddress[] ipAddresses;
+    public NetworkInformation(String name, ConnectionType type, int handle,
+                              IPAddress[] addresses) {
+      this.name = name;
+      this.type = type;
+      this.handle = handle;
+      this.ipAddresses = addresses;
+    }
+  };
+
   static class NetworkState {
     private final boolean connected;
     // Defined from ConnectivityManager.TYPE_XXX for non-mobile; for mobile, it is
@@ -101,6 +128,7 @@
      *  gracefully below.
      */
     private final ConnectivityManager connectivityManager;
+    private NetworkCallback networkCallback;
 
     ConnectivityManagerDelegate(Context context) {
       connectivityManager =
@@ -211,30 +239,69 @@
           connectivityManager.getNetworkCapabilities(network);
       return capabilities != null && capabilities.hasCapability(NET_CAPABILITY_INTERNET);
     }
+
+    @SuppressLint("NewApi")
+    public void requestMobileNetwork(final Observer observer) {
+      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ||
+          connectivityManager == null) {
+        return;
+      }
+      networkCallback = new NetworkCallback() {
+        @Override
+        public void onAvailable(Network network) {
+          super.onAvailable(network);
+          LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
+          NetworkInformation networkInformation = new NetworkInformation(
+            linkProperties.getInterfaceName(),
+            getConnectionType(getNetworkState(network)),
+            networkToNetId(network),
+            getIPAddresses(linkProperties));
+          Logging.d(TAG, "Network " + networkInformation.name + " is connected ");
+          observer.onNetworkConnect(networkInformation);
+        }
+      };
+      Logging.d(TAG, "Requesting cellular network");
+      NetworkRequest.Builder builder = new NetworkRequest.Builder();
+      builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+      builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+      connectivityManager.requestNetwork(builder.build(), networkCallback);
+    }
+
+    @SuppressLint("NewApi")
+    IPAddress[] getIPAddresses(LinkProperties linkProperties) {
+      IPAddress[] ipAddresses = new IPAddress[linkProperties.getLinkAddresses().size()];
+      int i = 0;
+      for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) {
+        ipAddresses[i] = new IPAddress(linkAddress.getAddress().getAddress());
+        ++i;
+      }
+      return ipAddresses;
+    }
+
+    @SuppressLint("NewApi")
+    public void releaseCallback() {
+      if (networkCallback != null) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+          connectivityManager.unregisterNetworkCallback(networkCallback);
+        }
+        networkCallback = null;
+      }
+    }
+
   }
 
+
   /** 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() {
@@ -252,9 +319,6 @@
       return "";
     }
 
-    boolean getHasWifiPermission() {
-      return hasWifiPermission;
-    }
   }
 
   static final int INVALID_NET_ID = -1;
@@ -280,6 +344,7 @@
      * Called when default network changes.
      */
     public void onConnectionTypeChanged(ConnectionType newConnectionType);
+    public void onNetworkConnect(NetworkInformation networkInfo);
   }
 
   /**
@@ -292,8 +357,8 @@
     wifiManagerDelegate = new WifiManagerDelegate(context);
 
     final NetworkState networkState = connectivityManagerDelegate.getNetworkState();
-    connectionType = getCurrentConnectionType(networkState);
-    wifiSSID = getCurrentWifiSSID(networkState);
+    connectionType = getConnectionType(networkState);
+    wifiSSID = getWifiSSID(networkState);
     intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
     registerReceiver();
   }
@@ -331,6 +396,7 @@
     if (!isRegistered) {
       isRegistered = true;
       context.registerReceiver(this, intentFilter);
+      connectivityManagerDelegate.requestMobileNetwork(observer);
     }
   }
 
@@ -341,6 +407,7 @@
     if (isRegistered) {
       isRegistered = false;
       context.unregisterReceiver(this);
+      connectivityManagerDelegate.releaseCallback();
     }
   }
 
@@ -361,7 +428,7 @@
     return connectivityManagerDelegate.getDefaultNetId();
   }
 
-  public ConnectionType getCurrentConnectionType(NetworkState networkState) {
+  public static ConnectionType getConnectionType(NetworkState networkState) {
     if (!networkState.isConnected()) {
       return ConnectionType.CONNECTION_NONE;
     }
@@ -404,8 +471,8 @@
     }
   }
 
-  private String getCurrentWifiSSID(NetworkState networkState) {
-    if (getCurrentConnectionType(networkState) != ConnectionType.CONNECTION_WIFI) return "";
+  private String getWifiSSID(NetworkState networkState) {
+    if (getConnectionType(networkState) != ConnectionType.CONNECTION_WIFI) return "";
     return wifiManagerDelegate.getWifiSSID();
   }
 
@@ -419,13 +486,13 @@
   }
 
   private void connectionTypeChanged(NetworkState networkState) {
-    ConnectionType newConnectionType = getCurrentConnectionType(networkState);
-    String newWifiSSID = getCurrentWifiSSID(networkState);
+    ConnectionType newConnectionType = getConnectionType(networkState);
+    String newWifiSSID = getWifiSSID(networkState);
     if (newConnectionType == connectionType && newWifiSSID.equals(wifiSSID)) return;
 
     connectionType = newConnectionType;
     wifiSSID = newWifiSSID;
-    Log.d(TAG, "Network connectivity changed, type is: " + connectionType);
+    Logging.d(TAG, "Network connectivity changed, type is: " + connectionType);
     observer.onConnectionTypeChanged(newConnectionType);
   }
 
diff --git a/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.cc b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.cc
index f7a8c07..1716c19 100644
--- a/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.cc
+++ b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.cc
@@ -27,13 +27,125 @@
 
 #include "talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h"
 
+#include <dlfcn.h>
+
+#include "webrtc/base/bind.h"
 #include "webrtc/base/common.h"
+#include "webrtc/base/ipaddress.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 NetworkType GetNetworkTypeFromJava(JNIEnv* jni, jobject j_network_type) {
+  std::string enum_name =
+      GetJavaEnumName(jni, "org/webrtc/NetworkMonitorAutoDetect$ConnectionType",
+                      j_network_type);
+  if (enum_name == "CONNECTION_UNKNOWN") {
+    return NetworkType::NETWORK_UNKNOWN;
+  }
+  if (enum_name == "CONNECTION_ETHERNET") {
+    return NetworkType::NETWORK_ETHERNET;
+  }
+  if (enum_name == "CONNECTION_WIFI") {
+    return NetworkType::NETWORK_WIFI;
+  }
+  if (enum_name == "CONNECTION_4G") {
+    return NetworkType::NETWORK_4G;
+  }
+  if (enum_name == "CONNECTION_3G") {
+    return NetworkType::NETWORK_3G;
+  }
+  if (enum_name == "CONNECTION_2G") {
+    return NetworkType::NETWORK_2G;
+  }
+  if (enum_name == "CONNECTION_BLUETOOTH") {
+    return NetworkType::NETWORK_BLUETOOTH;
+  }
+  if (enum_name == "CONNECTION_NONE") {
+    return NetworkType::NETWORK_NONE;
+  }
+  ASSERT(false);
+  return NetworkType::NETWORK_UNKNOWN;
+}
+
+static rtc::IPAddress GetIPAddressFromJava(JNIEnv* jni, jobject j_ip_address) {
+  jclass j_ip_address_class = GetObjectClass(jni, j_ip_address);
+  jfieldID j_address_id = GetFieldID(jni, j_ip_address_class, "address", "[B");
+  jbyteArray j_addresses =
+      static_cast<jbyteArray>(GetObjectField(jni, j_ip_address, j_address_id));
+  size_t address_length = jni->GetArrayLength(j_addresses);
+  jbyte* addr_array = jni->GetByteArrayElements(j_addresses, nullptr);
+  CHECK_EXCEPTION(jni) << "Error during GetIPAddressFromJava";
+  if (address_length == 4) {
+    // IP4
+    struct in_addr ip4_addr;
+    memcpy(&ip4_addr.s_addr, addr_array, 4);
+    jni->ReleaseByteArrayElements(j_addresses, addr_array, JNI_ABORT);
+    return rtc::IPAddress(ip4_addr);
+  }
+  // IP6
+  RTC_CHECK(address_length == 16);
+  struct in6_addr ip6_addr;
+  memcpy(ip6_addr.s6_addr, addr_array, address_length);
+  jni->ReleaseByteArrayElements(j_addresses, addr_array, JNI_ABORT);
+  return rtc::IPAddress(ip6_addr);
+}
+
+static void GetIPAddressesFromJava(JNIEnv* jni,
+                                   jobjectArray j_ip_addresses,
+                                   std::vector<rtc::IPAddress>* ip_addresses) {
+  ip_addresses->clear();
+  size_t num_addresses = jni->GetArrayLength(j_ip_addresses);
+  CHECK_EXCEPTION(jni) << "Error during GetArrayLength";
+  for (size_t i = 0; i < num_addresses; ++i) {
+    jobject j_ip_address = jni->GetObjectArrayElement(j_ip_addresses, i);
+    CHECK_EXCEPTION(jni) << "Error during GetObjectArrayElement";
+    rtc::IPAddress ip = GetIPAddressFromJava(jni, j_ip_address);
+    ip_addresses->push_back(ip);
+  }
+}
+
+static NetworkInformation GetNetworkInformationFromJava(
+    JNIEnv* jni,
+    jobject j_network_info) {
+  jclass j_network_info_class = GetObjectClass(jni, j_network_info);
+  jfieldID j_interface_name_id =
+      GetFieldID(jni, j_network_info_class, "name", "Ljava/lang/String;");
+  jfieldID j_handle_id = GetFieldID(jni, j_network_info_class, "handle", "I");
+  jfieldID j_type_id =
+      GetFieldID(jni, j_network_info_class, "type",
+                 "Lorg/webrtc/NetworkMonitorAutoDetect$ConnectionType;");
+  jfieldID j_ip_addresses_id =
+      GetFieldID(jni, j_network_info_class, "ipAddresses",
+                 "[Lorg/webrtc/NetworkMonitorAutoDetect$IPAddress;");
+
+  NetworkInformation network_info;
+  network_info.interface_name = JavaToStdString(
+      jni, GetStringField(jni, j_network_info, j_interface_name_id));
+  network_info.handle =
+      static_cast<NetworkHandle>(GetIntField(jni, j_network_info, j_handle_id));
+  network_info.type = GetNetworkTypeFromJava(
+      jni, GetObjectField(jni, j_network_info, j_type_id));
+  jobjectArray j_ip_addresses = static_cast<jobjectArray>(
+      GetObjectField(jni, j_network_info, j_ip_addresses_id));
+  GetIPAddressesFromJava(jni, j_ip_addresses, &network_info.ip_addresses);
+  return network_info;
+}
+
+std::string NetworkInformation::ToString() const {
+  std::stringstream ss;
+  ss << "NetInfo[name " << interface_name << "; handle " << handle << "; type "
+     << type << "; address";
+  for (const rtc::IPAddress address : ip_addresses) {
+    ss << " " << address.ToString();
+  }
+  ss << "]";
+  return ss.str();
+}
+
 // static
 void AndroidNetworkMonitor::SetAndroidContext(JNIEnv* jni, jobject context) {
   if (application_context_) {
@@ -61,6 +173,16 @@
 
 void AndroidNetworkMonitor::Start() {
   RTC_CHECK(thread_checker_.CalledOnValidThread());
+  if (started_) {
+    return;
+  }
+  started_ = true;
+
+  // This is kind of magic behavior, but doing this allows the SocketServer to
+  // use this as a NetworkBinder to bind sockets on a particular network when
+  // it creates sockets.
+  worker_thread()->socketserver()->set_network_binder(this);
+
   jmethodID m =
       GetMethodID(jni(), *j_network_monitor_class_, "startMonitoring", "(J)V");
   jni()->CallVoidMethod(*j_network_monitor_, m, jlongFromPointer(this));
@@ -69,10 +191,89 @@
 
 void AndroidNetworkMonitor::Stop() {
   RTC_CHECK(thread_checker_.CalledOnValidThread());
+  if (!started_) {
+    return;
+  }
+  started_ = false;
+
+  // Once the network monitor stops, it will clear all network information and
+  // it won't find the network handle to bind anyway.
+  if (worker_thread()->socketserver()->network_binder() == this) {
+    worker_thread()->socketserver()->set_network_binder(nullptr);
+  }
+
   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";
+
+  network_info_by_address_.clear();
+}
+
+int AndroidNetworkMonitor::BindSocketToNetwork(int socket_fd,
+                                               const rtc::IPAddress& address) {
+  RTC_CHECK(thread_checker_.CalledOnValidThread());
+  auto it = network_info_by_address_.find(address);
+  if (it == network_info_by_address_.end()) {
+    return rtc::NETWORK_BIND_ADDRESS_NOT_FOUND;
+  }
+  // Android prior to Lollipop didn't have support for binding sockets to
+  // networks. However, in that case it should not have reached here because
+  // |network_info_by_address_| should only be populated in Android Lollipop
+  // and above.
+  NetworkInformation network = it->second;
+
+  // NOTE: 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 not threadsafe, but we are running this only on the worker thread.
+  if (setNetworkForSocket == nullptr) {
+    // Android's netd client library should always be loaded in our address
+    // space as it shims libc functions like connect().
+    const std::string net_library_path = "libnetd_client.so";
+    void* lib = dlopen(net_library_path.c_str(), RTLD_LAZY);
+    if (lib == nullptr) {
+      LOG(LS_ERROR) << "Library " << net_library_path << " not found!";
+      return rtc::NETWORK_BIND_NOT_IMPLEMENTED;
+    }
+    setNetworkForSocket = reinterpret_cast<SetNetworkForSocket>(
+        dlsym(lib, "setNetworkForSocket"));
+  }
+  if (setNetworkForSocket == nullptr) {
+    LOG(LS_ERROR) << "Symbol setNetworkForSocket not found ";
+    return rtc::NETWORK_BIND_NOT_IMPLEMENTED;
+  }
+  int rv = setNetworkForSocket(network.handle, socket_fd);
+  // If |network| has since disconnected, |rv| will be ENONET.  Surface this as
+  // ERR_NETWORK_CHANGED, rather than MapSystemError(ENONET) which gives back
+  // the less descriptive ERR_FAILED.
+  if (rv == 0) {
+    return rtc::NETWORK_BIND_SUCCESS;
+  }
+  if (rv == ENONET) {
+    return rtc::NETWORK_BIND_NETWORK_CHANGED;
+  }
+  return rtc::NETWORK_BIND_FAILURE;
+}
+
+void AndroidNetworkMonitor::OnNetworkAvailable(
+    const NetworkInformation& network_info) {
+  worker_thread()->Invoke<void>(rtc::Bind(
+      &AndroidNetworkMonitor::OnNetworkAvailable_w, this, network_info));
+}
+
+void AndroidNetworkMonitor::OnNetworkAvailable_w(
+    const NetworkInformation& network_info) {
+  LOG(LS_INFO) << "Network available: " << network_info.ToString();
+  for (rtc::IPAddress address : network_info.ip_addresses) {
+    network_info_by_address_[address] = network_info;
+  }
+}
+
+rtc::NetworkMonitorInterface*
+AndroidNetworkMonitorFactory::CreateNetworkMonitor() {
+  return new AndroidNetworkMonitor();
 }
 
 JOW(void, NetworkMonitor_nativeNotifyConnectionTypeChanged)(
@@ -82,4 +283,14 @@
   network_monitor->OnNetworksChanged();
 }
 
+JOW(void, NetworkMonitor_nativeNotifyOfNetworkConnect)(
+    JNIEnv* jni, jobject j_monitor, jlong j_native_monitor,
+    jobject j_network_info) {
+  AndroidNetworkMonitor* network_monitor =
+      reinterpret_cast<AndroidNetworkMonitor*>(j_native_monitor);
+  NetworkInformation network_info =
+      GetNetworkInformationFromJava(jni, j_network_info);
+  network_monitor->OnNetworkAvailable(network_info);
+}
+
 }  // namespace webrtc_jni
diff --git a/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h
index 3f5110c..17de360 100644
--- a/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h
+++ b/talk/app/webrtc/java/jni/androidnetworkmonitor_jni.h
@@ -30,12 +30,41 @@
 
 #include "webrtc/base/networkmonitor.h"
 
+#include <map>
+
+#include "webrtc/base/basictypes.h"
 #include "webrtc/base/thread_checker.h"
 #include "talk/app/webrtc/java/jni/jni_helpers.h"
 
 namespace webrtc_jni {
 
-class AndroidNetworkMonitor : public rtc::NetworkMonitorBase {
+typedef uint32_t NetworkHandle;
+
+// c++ equivalent of java NetworkMonitorAutoDetect.ConnectionType.
+enum NetworkType {
+  NETWORK_UNKNOWN,
+  NETWORK_ETHERNET,
+  NETWORK_WIFI,
+  NETWORK_4G,
+  NETWORK_3G,
+  NETWORK_2G,
+  NETWORK_BLUETOOTH,
+  NETWORK_NONE
+};
+
+// The information is collected from Android OS so that the native code can get
+// the network type and handle (Android network ID) for each interface.
+struct NetworkInformation {
+  std::string interface_name;
+  NetworkHandle handle;
+  NetworkType type;
+  std::vector<rtc::IPAddress> ip_addresses;
+
+  std::string ToString() const;
+};
+
+class AndroidNetworkMonitor : public rtc::NetworkMonitorBase,
+                              public rtc::NetworkBinderInterface {
  public:
   AndroidNetworkMonitor();
 
@@ -44,22 +73,28 @@
   void Start() override;
   void Stop() override;
 
+  int BindSocketToNetwork(int socket_fd,
+                          const rtc::IPAddress& address) override;
+  void OnNetworkAvailable(const NetworkInformation& network_info);
+
  private:
   JNIEnv* jni() { return AttachCurrentThreadIfNeeded(); }
 
+  void OnNetworkAvailable_w(const NetworkInformation& network_info);
+
   ScopedGlobalRef<jclass> j_network_monitor_class_;
   ScopedGlobalRef<jobject> j_network_monitor_;
   rtc::ThreadChecker thread_checker_;
   static jobject application_context_;
+  bool started_ = false;
+  std::map<rtc::IPAddress, NetworkInformation> network_info_by_address_;
 };
 
 class AndroidNetworkMonitorFactory : public rtc::NetworkMonitorFactory {
  public:
   AndroidNetworkMonitorFactory() {}
 
-  rtc::NetworkMonitorInterface* CreateNetworkMonitor() override {
-    return new AndroidNetworkMonitor();
-  }
+  rtc::NetworkMonitorInterface* CreateNetworkMonitor() override;
 };
 
 }  // namespace webrtc_jni
diff --git a/talk/app/webrtc/java/jni/classreferenceholder.cc b/talk/app/webrtc/java/jni/classreferenceholder.cc
index 5fe8ec7..a7d36c2 100644
--- a/talk/app/webrtc/java/jni/classreferenceholder.cc
+++ b/talk/app/webrtc/java/jni/classreferenceholder.cc
@@ -81,6 +81,9 @@
   LoadClass(jni, "org/webrtc/EglBase$Context");
   LoadClass(jni, "org/webrtc/EglBase14$Context");
   LoadClass(jni, "org/webrtc/NetworkMonitor");
+  LoadClass(jni, "org/webrtc/NetworkMonitorAutoDetect$ConnectionType");
+  LoadClass(jni, "org/webrtc/NetworkMonitorAutoDetect$IPAddress");
+  LoadClass(jni, "org/webrtc/NetworkMonitorAutoDetect$NetworkInformation");
   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/jni_helpers.cc b/talk/app/webrtc/java/jni/jni_helpers.cc
index 3a7ff21..25b340f 100644
--- a/talk/app/webrtc/java/jni/jni_helpers.cc
+++ b/talk/app/webrtc/java/jni/jni_helpers.cc
@@ -27,6 +27,8 @@
  */
 #include "talk/app/webrtc/java/jni/jni_helpers.h"
 
+#include "talk/app/webrtc/java/jni/classreferenceholder.h"
+
 #include <asm/unistd.h>
 #include <sys/prctl.h>
 #include <sys/syscall.h>
@@ -256,6 +258,19 @@
   return ret;
 }
 
+std::string GetJavaEnumName(JNIEnv* jni,
+                            const std::string& className,
+                            jobject j_enum) {
+  jclass enumClass = FindClass(jni, className.c_str());
+  jmethodID nameMethod =
+      GetMethodID(jni, enumClass, "name", "()Ljava/lang/String;");
+  jstring name =
+      reinterpret_cast<jstring>(jni->CallObjectMethod(j_enum, nameMethod));
+  CHECK_EXCEPTION(jni) << "error during CallObjectMethod for " << className
+                       << ".name";
+  return JavaToStdString(jni, name);
+}
+
 jobject NewGlobalRef(JNIEnv* jni, jobject o) {
   jobject ret = jni->NewGlobalRef(o);
   CHECK_EXCEPTION(jni) << "error during NewGlobalRef";
diff --git a/talk/app/webrtc/java/jni/jni_helpers.h b/talk/app/webrtc/java/jni/jni_helpers.h
index 7072ee8..374962b 100644
--- a/talk/app/webrtc/java/jni/jni_helpers.h
+++ b/talk/app/webrtc/java/jni/jni_helpers.h
@@ -104,6 +104,11 @@
 jobject JavaEnumFromIndex(JNIEnv* jni, jclass state_class,
                           const std::string& state_class_name, int index);
 
+// Returns the name of a Java enum.
+std::string GetJavaEnumName(JNIEnv* jni,
+                            const std::string& className,
+                            jobject j_enum);
+
 jobject NewGlobalRef(JNIEnv* jni, jobject o);
 
 void DeleteGlobalRef(JNIEnv* jni, jobject o);
diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc
index f80f576..6059623 100644
--- a/talk/app/webrtc/java/jni/peerconnection_jni.cc
+++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc
@@ -1395,18 +1395,6 @@
 #endif
 }
 
-static std::string
-GetJavaEnumName(JNIEnv* jni, const std::string& className, jobject j_enum) {
-  jclass enumClass = FindClass(jni, className.c_str());
-  jmethodID nameMethod =
-      GetMethodID(jni, enumClass, "name", "()Ljava/lang/String;");
-  jstring name =
-      reinterpret_cast<jstring>(jni->CallObjectMethod(j_enum, nameMethod));
-  CHECK_EXCEPTION(jni) << "error during CallObjectMethod for "
-                       << className << ".name";
-  return JavaToStdString(jni, name);
-}
-
 static PeerConnectionInterface::IceTransportsType
 JavaIceTransportsTypeToNativeType(JNIEnv* jni, jobject j_ice_transports_type) {
   std::string enum_name = GetJavaEnumName(