Add the network preference to RTCConfiguration.

The network preference is added to RTCConfiguration and passed to ICE.
ICE considers now the preference set by applications over network
interface types when making decisions in candidate pair switching.

Bug: webrtc:8816
Change-Id: I40d2612705b54c83dd45772ac855808e0a76b1e1
Reviewed-on: https://webrtc-review.googlesource.com/44020
Commit-Queue: Qingsi Wang <qingsi@google.com>
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21855}
diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h
index 7087de6..0125455 100644
--- a/api/peerconnectioninterface.h
+++ b/api/peerconnectioninterface.h
@@ -491,6 +491,12 @@
     // called.
     webrtc::TurnCustomizer* turn_customizer = nullptr;
 
+    // Preferred network interface.
+    // A candidate pair on a preferred network has a higher precedence in ICE
+    // than one on an un-preferred network, regardless of priority or network
+    // cost.
+    rtc::Optional<rtc::AdapterType> network_preference;
+
     // Configure the SDP semantics used by this PeerConnection. Note that the
     // WebRTC 1.0 specification requires kUnifiedPlan semantics. The
     // RtpTransceiver API is only available with kUnifiedPlan semantics.
diff --git a/p2p/base/icetransportinternal.cc b/p2p/base/icetransportinternal.cc
index e4e56fe..1edbf63 100644
--- a/p2p/base/icetransportinternal.cc
+++ b/p2p/base/icetransportinternal.cc
@@ -21,7 +21,8 @@
                      int stable_writable_connection_ping_interval_ms,
                      bool presume_writable_when_fully_relayed,
                      int regather_on_failed_networks_interval_ms,
-                     int receiving_switching_delay_ms)
+                     int receiving_switching_delay_ms,
+                     rtc::Optional<rtc::AdapterType> network_preference)
     : receiving_timeout(receiving_timeout_ms),
       backup_connection_ping_interval(backup_connection_ping_interval),
       continual_gathering_policy(gathering_policy),
@@ -32,7 +33,8 @@
       presume_writable_when_fully_relayed(presume_writable_when_fully_relayed),
       regather_on_failed_networks_interval(
           regather_on_failed_networks_interval_ms),
-      receiving_switching_delay(receiving_switching_delay_ms) {}
+      receiving_switching_delay(receiving_switching_delay_ms),
+      network_preference(network_preference) {}
 
 IceConfig::~IceConfig() = default;
 
diff --git a/p2p/base/icetransportinternal.h b/p2p/base/icetransportinternal.h
index 787f66e..6888992 100644
--- a/p2p/base/icetransportinternal.h
+++ b/p2p/base/icetransportinternal.h
@@ -115,6 +115,8 @@
   // Measure in milliseconds.
   rtc::Optional<int> ice_check_min_interval;
 
+  rtc::Optional<rtc::AdapterType> network_preference;
+
   IceConfig();
   IceConfig(int receiving_timeout_ms,
             int backup_connection_ping_interval,
@@ -123,7 +125,8 @@
             int stable_writable_connection_ping_interval_ms,
             bool presume_writable_when_fully_relayed,
             int regather_on_failed_networks_interval_ms,
-            int receiving_switching_delay_ms);
+            int receiving_switching_delay_ms,
+            rtc::Optional<rtc::AdapterType> network_preference);
   ~IceConfig();
 };
 
diff --git a/p2p/base/p2ptransportchannel.cc b/p2p/base/p2ptransportchannel.cc
index ddd2403..02b943a 100644
--- a/p2p/base/p2ptransportchannel.cc
+++ b/p2p/base/p2ptransportchannel.cc
@@ -61,6 +61,35 @@
     return cricket::PortInterface::ORIGIN_OTHER_PORT;
 }
 
+// TODO(qingsi) Use an enum to replace the following constants for all
+// comparision results.
+static constexpr int a_is_better = 1;
+static constexpr int b_is_better = -1;
+static constexpr int a_and_b_equal = 0;
+
+bool LocalCandidateUsesPreferredNetwork(
+    const cricket::Connection* conn,
+    rtc::Optional<rtc::AdapterType> network_preference) {
+  rtc::AdapterType network_type = conn->port()->Network()->type();
+  return network_preference.has_value() && (network_type == network_preference);
+}
+
+int CompareCandidatePairsByNetworkPreference(
+    const cricket::Connection* a,
+    const cricket::Connection* b,
+    rtc::Optional<rtc::AdapterType> network_preference) {
+  bool a_uses_preferred_network =
+      LocalCandidateUsesPreferredNetwork(a, network_preference);
+  bool b_uses_preferred_network =
+      LocalCandidateUsesPreferredNetwork(b, network_preference);
+  if (a_uses_preferred_network && !b_uses_preferred_network) {
+    return a_is_better;
+  } else if (!a_uses_preferred_network && b_uses_preferred_network) {
+    return b_is_better;
+  }
+  return a_and_b_equal;
+}
+
 }  // unnamed namespace
 
 namespace cricket {
@@ -98,9 +127,6 @@
 
 static constexpr int DEFAULT_BACKUP_CONNECTION_PING_INTERVAL = 25 * 1000;
 
-static constexpr int a_is_better = 1;
-static constexpr int b_is_better = -1;
-
 bool IceCredentialsChanged(const std::string& old_ufrag,
                            const std::string& old_pwd,
                            const std::string& new_ufrag,
@@ -135,7 +161,8 @@
               STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL,
               true /* presume_writable_when_fully_relayed */,
               DEFAULT_REGATHER_ON_FAILED_NETWORKS_INTERVAL,
-              RECEIVING_SWITCHING_DELAY) {
+              RECEIVING_SWITCHING_DELAY,
+              rtc::nullopt) {
   uint32_t weak_ping_interval = ::strtoul(
       webrtc::field_trial::FindFullName("WebRTC-StunInterPacketDelay").c_str(),
       nullptr, 10);
@@ -218,11 +245,12 @@
     return true;
   }
 
-  // Do not switch to a connection that is not receiving if it has higher cost
-  // because it may be just spuriously better.
-  if (new_connection->ComputeNetworkCost() >
-          selected_connection_->ComputeNetworkCost() &&
-      !new_connection->receiving()) {
+  // Do not switch to a connection that is not receiving if it is not on a
+  // preferred network or it has higher cost because it may be just spuriously
+  // better.
+  int compare_a_b_by_networks = CompareCandidatePairNetworks(
+      new_connection, selected_connection_, config_.network_preference);
+  if (compare_a_b_by_networks == b_is_better && !new_connection->receiving()) {
     return false;
   }
 
@@ -497,6 +525,14 @@
     RTC_LOG(LS_INFO) << "Set min ping interval to "
                      << *config_.ice_check_min_interval;
   }
+
+  if (config_.network_preference != config.network_preference) {
+    config_.network_preference = config.network_preference;
+    RTC_LOG(LS_INFO) << "Set network preference to "
+                     << (config_.network_preference.has_value()
+                             ? config_.network_preference.value()
+                             : 0);
+  }
 }
 
 const IceConfig& P2PTransportChannel::config() const {
@@ -1150,6 +1186,30 @@
   }
 }
 
+int P2PTransportChannel::CompareCandidatePairNetworks(
+    const Connection* a,
+    const Connection* b,
+    rtc::Optional<rtc::AdapterType> network_preference) const {
+  int compare_a_b_by_network_preference =
+      CompareCandidatePairsByNetworkPreference(a, b,
+                                               config_.network_preference);
+  // The network preference has a higher precedence than the network cost.
+  if (compare_a_b_by_network_preference != a_and_b_equal) {
+    return compare_a_b_by_network_preference;
+  }
+
+  uint32_t a_cost = a->ComputeNetworkCost();
+  uint32_t b_cost = b->ComputeNetworkCost();
+  // Prefer lower network cost.
+  if (a_cost < b_cost) {
+    return a_is_better;
+  }
+  if (a_cost > b_cost) {
+    return b_is_better;
+  }
+  return a_and_b_equal;
+}
+
 // Compare two connections based on their writing, receiving, and connected
 // states.
 int P2PTransportChannel::CompareConnectionStates(
@@ -1230,15 +1290,10 @@
 int P2PTransportChannel::CompareConnectionCandidates(
     const Connection* a,
     const Connection* b) const {
-  // Prefer lower network cost.
-  uint32_t a_cost = a->ComputeNetworkCost();
-  uint32_t b_cost = b->ComputeNetworkCost();
-  // Smaller cost is better.
-  if (a_cost < b_cost) {
-    return a_is_better;
-  }
-  if (a_cost > b_cost) {
-    return b_is_better;
+  int compare_a_b_by_networks =
+      CompareCandidatePairNetworks(a, b, config_.network_preference);
+  if (compare_a_b_by_networks != a_and_b_equal) {
+    return compare_a_b_by_networks;
   }
 
   // Compare connection priority. Lower values get sorted last.
diff --git a/p2p/base/p2ptransportchannel.h b/p2p/base/p2ptransportchannel.h
index c2febbf..ed0be2e 100644
--- a/p2p/base/p2ptransportchannel.h
+++ b/p2p/base/p2ptransportchannel.h
@@ -195,6 +195,11 @@
   // that's pingable.
   void MaybeStartPinging();
 
+  int CompareCandidatePairNetworks(
+      const Connection* a,
+      const Connection* b,
+      rtc::Optional<rtc::AdapterType> network_preference) const;
+
   // The methods below return a positive value if |a| is preferable to |b|,
   // a negative value if |b| is preferable, and 0 if they're equally preferable.
   // If |receiving_unchanged_threshold| is set, then when |b| is receiving and
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index a629761..0596869 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -603,6 +603,7 @@
     rtc::Optional<rtc::IntervalRange> ice_regather_interval_range;
     webrtc::TurnCustomizer* turn_customizer;
     SdpSemantics sdp_semantics;
+    rtc::Optional<rtc::AdapterType> network_preference;
   };
   static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this),
                 "Did you add something to RTCConfiguration and forget to "
@@ -639,7 +640,8 @@
          ice_check_min_interval == o.ice_check_min_interval &&
          ice_regather_interval_range == o.ice_regather_interval_range &&
          turn_customizer == o.turn_customizer &&
-         sdp_semantics == o.sdp_semantics;
+         sdp_semantics == o.sdp_semantics &&
+         network_preference == o.network_preference;
 }
 
 bool PeerConnectionInterface::RTCConfiguration::operator!=(
@@ -2519,6 +2521,7 @@
   modified_config.prune_turn_ports = configuration.prune_turn_ports;
   modified_config.ice_check_min_interval = configuration.ice_check_min_interval;
   modified_config.turn_customizer = configuration.turn_customizer;
+  modified_config.network_preference = configuration.network_preference;
   if (configuration != modified_config) {
     RTC_LOG(LS_ERROR) << "Modifying the configuration in an unsupported way.";
     return SafeSetError(RTCErrorType::INVALID_MODIFICATION, error);
@@ -4613,6 +4616,7 @@
       RTC_NOTREACHED();
       gathering_policy = cricket::GATHER_ONCE;
   }
+
   cricket::IceConfig ice_config;
   ice_config.receiving_timeout = config.ice_connection_receiving_timeout;
   ice_config.prioritize_most_likely_candidate_pairs =
@@ -4625,6 +4629,7 @@
   ice_config.ice_check_min_interval = config.ice_check_min_interval;
   ice_config.regather_all_networks_interval_range =
       config.ice_regather_interval_range;
+  ice_config.network_preference = config.network_preference;
   return ice_config;
 }
 
diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java
index 20b06d5..c6598b7 100644
--- a/sdk/android/api/org/webrtc/PeerConnection.java
+++ b/sdk/android/api/org/webrtc/PeerConnection.java
@@ -304,6 +304,16 @@
   /** Java version of PeerConnectionInterface.CandidateNetworkPolicy */
   public enum CandidateNetworkPolicy { ALL, LOW_COST }
 
+  // Keep in sync with webrtc/rtc_base/network_constants.h.
+  public enum AdapterType {
+    UNKNOWN,
+    ETHERNET,
+    WIFI,
+    CELLULAR,
+    VPN,
+    LOOPBACK,
+  }
+
   /** Java version of rtc::KeyType */
   public enum KeyType { RSA, ECDSA }
 
@@ -368,6 +378,9 @@
     public Integer screencastMinBitrate;
     public Boolean combinedAudioVideoBwe;
     public Boolean enableDtlsSrtp;
+    // Use "Unknown" to represent no preference of adapter types, not the
+    // preference of adapters of unknown types.
+    public AdapterType networkPreference;
 
     // This is an optional wrapper for the C++ webrtc::TurnCustomizer.
     public TurnCustomizer turnCustomizer;
@@ -403,6 +416,7 @@
       screencastMinBitrate = null;
       combinedAudioVideoBwe = null;
       enableDtlsSrtp = null;
+      networkPreference = AdapterType.UNKNOWN;
     }
 
     @CalledByNative("RTCConfiguration")
@@ -544,6 +558,11 @@
     Boolean getEnableDtlsSrtp() {
       return enableDtlsSrtp;
     }
+
+    @CalledByNative("RTCConfiguration")
+    AdapterType getNetworkPreference() {
+      return networkPreference;
+    }
   };
 
   private final List<MediaStream> localStreams = new ArrayList<>();
diff --git a/sdk/android/api/org/webrtc/PeerConnectionFactory.java b/sdk/android/api/org/webrtc/PeerConnectionFactory.java
index 90b4cb6..71e4b6c 100644
--- a/sdk/android/api/org/webrtc/PeerConnectionFactory.java
+++ b/sdk/android/api/org/webrtc/PeerConnectionFactory.java
@@ -95,6 +95,8 @@
 
   public static class Options {
     // Keep in sync with webrtc/rtc_base/network.h!
+    //
+    // These bit fields are defined for |networkIgnoreMask| below.
     static final int ADAPTER_TYPE_UNKNOWN = 0;
     static final int ADAPTER_TYPE_ETHERNET = 1 << 0;
     static final int ADAPTER_TYPE_WIFI = 1 << 1;
diff --git a/sdk/android/src/jni/pc/icecandidate.cc b/sdk/android/src/jni/pc/icecandidate.cc
index e6fad74..84e0f6e 100644
--- a/sdk/android/src/jni/pc/icecandidate.cc
+++ b/sdk/android/src/jni/pc/icecandidate.cc
@@ -207,5 +207,32 @@
   return PeerConnectionInterface::kTlsCertPolicySecure;
 }
 
+rtc::Optional<rtc::AdapterType> JavaToNativeNetworkPreference(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_network_preference) {
+  std::string enum_name = GetJavaEnumName(jni, j_network_preference);
+
+  if (enum_name == "UNKNOWN")
+    return rtc::nullopt;
+
+  if (enum_name == "ETHERNET")
+    return rtc::ADAPTER_TYPE_ETHERNET;
+
+  if (enum_name == "WIFI")
+    return rtc::ADAPTER_TYPE_WIFI;
+
+  if (enum_name == "CELLULAR")
+    return rtc::ADAPTER_TYPE_CELLULAR;
+
+  if (enum_name == "VPN")
+    return rtc::ADAPTER_TYPE_VPN;
+
+  if (enum_name == "LOOPBACK")
+    return rtc::ADAPTER_TYPE_LOOPBACK;
+
+  RTC_CHECK(false) << "Unexpected NetworkPreference enum_name " << enum_name;
+  return rtc::nullopt;
+}
+
 }  // namespace jni
 }  // namespace webrtc
diff --git a/sdk/android/src/jni/pc/icecandidate.h b/sdk/android/src/jni/pc/icecandidate.h
index 324aaa6..be4d27c 100644
--- a/sdk/android/src/jni/pc/icecandidate.h
+++ b/sdk/android/src/jni/pc/icecandidate.h
@@ -75,6 +75,10 @@
     JNIEnv* jni,
     const JavaRef<jobject>& j_ice_server_tls_cert_policy);
 
+rtc::Optional<rtc::AdapterType> JavaToNativeNetworkPreference(
+    JNIEnv* jni,
+    const JavaRef<jobject>& j_network_preference);
+
 }  // namespace jni
 }  // namespace webrtc
 
diff --git a/sdk/android/src/jni/pc/peerconnection.cc b/sdk/android/src/jni/pc/peerconnection.cc
index 4055467..f6f3932 100644
--- a/sdk/android/src/jni/pc/peerconnection.cc
+++ b/sdk/android/src/jni/pc/peerconnection.cc
@@ -121,6 +121,8 @@
       Java_RTCConfiguration_getContinualGatheringPolicy(jni, j_rtc_config);
   ScopedJavaLocalRef<jobject> j_turn_customizer =
       Java_RTCConfiguration_getTurnCustomizer(jni, j_rtc_config);
+  ScopedJavaLocalRef<jobject> j_network_preference =
+      Java_RTCConfiguration_getNetworkPreference(jni, j_rtc_config);
 
   rtc_config->type = JavaToNativeIceTransportsType(jni, j_ice_transports_type);
   rtc_config->bundle_policy = JavaToNativeBundlePolicy(jni, j_bundle_policy);
@@ -184,6 +186,8 @@
       jni, Java_RTCConfiguration_getCombinedAudioVideoBwe(jni, j_rtc_config));
   rtc_config->enable_dtls_srtp = JavaToNativeOptionalBool(
       jni, Java_RTCConfiguration_getEnableDtlsSrtp(jni, j_rtc_config));
+  rtc_config->network_preference =
+      JavaToNativeNetworkPreference(jni, j_network_preference);
 }
 
 rtc::KeyType GetRtcConfigKeyType(JNIEnv* env,