Use active ICE controller in P2PTransportChannel with an adapter (#6/n)

Controlled by a field trial, P2PTransportChannel can now use an active ICE controller instead of a legacy ICE controller.

P2PTransportChannel unit tests need non-trivial changes to exercise the refactored code path, so the testing changes are added in a follow-up CL.

Bug: webrtc:14367, webrtc:14131
Change-Id: I00d4930a5692c7d6d331ea9d6c2a2199304e363c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/274701
Commit-Queue: Sameer Vijaykar <samvi@google.com>
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Reviewed-by: Per Kjellander <perkj@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38114}
diff --git a/api/ice_transport_interface.h b/api/ice_transport_interface.h
index ca67771..cd6ada8 100644
--- a/api/ice_transport_interface.h
+++ b/api/ice_transport_interface.h
@@ -89,7 +89,18 @@
   // transport, in contrast with a legacy ICE controller that only picks the
   // best connection to use or ping, and lets the transport decide when and
   // whether to switch.
-  // TODO(bugs.webrtc.org/14367): currently unused, update doc when used.
+  //
+  // Which ICE controller is used is determined based on the field trial
+  // "WebRTC-UseActiveIceController" as follows:
+  //
+  //   1. If the field trial is not enabled
+  //      a. The legacy ICE controller factory is used if one is supplied.
+  //      b. If not, a default ICE controller (BasicIceController) is
+  //      constructed and used.
+  //
+  //   2. If the field trial is enabled
+  //      - then an active ICE controller factory must be supplied and is used.
+  //      - the legacy ICE controller factory is not used in this case.
   void set_active_ice_controller_factory(
       cricket::ActiveIceControllerFactoryInterface*
           active_ice_controller_factory) {
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index 7e5f173..1009304 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -199,7 +199,9 @@
     sources = [
       "base/fake_dtls_transport.h",
       "base/fake_packet_transport.h",
+      "base/mock_active_ice_controller.h",
       "base/mock_async_resolver.h",
+      "base/mock_ice_controller.h",
       "base/mock_ice_transport.h",
       "base/test_stun_server.cc",
       "base/test_stun_server.h",
diff --git a/p2p/base/mock_active_ice_controller.h b/p2p/base/mock_active_ice_controller.h
new file mode 100644
index 0000000..908967b
--- /dev/null
+++ b/p2p/base/mock_active_ice_controller.h
@@ -0,0 +1,89 @@
+/*
+ *  Copyright 2018 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 P2P_BASE_MOCK_ACTIVE_ICE_CONTROLLER_H_
+#define P2P_BASE_MOCK_ACTIVE_ICE_CONTROLLER_H_
+
+#include <memory>
+
+#include "p2p/base/active_ice_controller_factory_interface.h"
+#include "p2p/base/active_ice_controller_interface.h"
+#include "test/gmock.h"
+
+namespace cricket {
+
+class MockActiveIceController : public cricket::ActiveIceControllerInterface {
+ public:
+  explicit MockActiveIceController(
+      const cricket::ActiveIceControllerFactoryArgs& args) {}
+  ~MockActiveIceController() override = default;
+
+  MOCK_METHOD(void, SetIceConfig, (const cricket::IceConfig&), (override));
+  MOCK_METHOD(void,
+              OnConnectionAdded,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(void,
+              OnConnectionSwitched,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(void,
+              OnConnectionDestroyed,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(void,
+              OnConnectionPinged,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(void,
+              OnConnectionUpdated,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(bool,
+              GetUseCandidateAttribute,
+              (const cricket::Connection*,
+               cricket::NominationMode,
+               cricket::IceMode),
+              (const, override));
+  MOCK_METHOD(void,
+              OnSortAndSwitchRequest,
+              (cricket::IceSwitchReason),
+              (override));
+  MOCK_METHOD(void,
+              OnImmediateSortAndSwitchRequest,
+              (cricket::IceSwitchReason),
+              (override));
+  MOCK_METHOD(bool,
+              OnImmediateSwitchRequest,
+              (cricket::IceSwitchReason, const cricket::Connection*),
+              (override));
+  MOCK_METHOD(const cricket::Connection*,
+              FindNextPingableConnection,
+              (),
+              (override));
+};
+
+class MockActiveIceControllerFactory
+    : public cricket::ActiveIceControllerFactoryInterface {
+ public:
+  ~MockActiveIceControllerFactory() override = default;
+
+  std::unique_ptr<cricket::ActiveIceControllerInterface> Create(
+      const cricket::ActiveIceControllerFactoryArgs& args) {
+    RecordActiveIceControllerCreated();
+    return std::make_unique<MockActiveIceController>(args);
+  }
+
+  MOCK_METHOD(void, RecordActiveIceControllerCreated, ());
+};
+
+}  // namespace cricket
+
+#endif  // P2P_BASE_MOCK_ACTIVE_ICE_CONTROLLER_H_
diff --git a/p2p/base/mock_ice_controller.h b/p2p/base/mock_ice_controller.h
new file mode 100644
index 0000000..bde9254
--- /dev/null
+++ b/p2p/base/mock_ice_controller.h
@@ -0,0 +1,90 @@
+/*
+ *  Copyright 2018 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 P2P_BASE_MOCK_ICE_CONTROLLER_H_
+#define P2P_BASE_MOCK_ICE_CONTROLLER_H_
+
+#include <memory>
+#include <vector>
+
+#include "p2p/base/ice_controller_factory_interface.h"
+#include "p2p/base/ice_controller_interface.h"
+#include "test/gmock.h"
+
+namespace cricket {
+
+class MockIceController : public cricket::IceControllerInterface {
+ public:
+  explicit MockIceController(const cricket::IceControllerFactoryArgs& args) {}
+  ~MockIceController() override = default;
+
+  MOCK_METHOD(void, SetIceConfig, (const cricket::IceConfig&), (override));
+  MOCK_METHOD(void,
+              SetSelectedConnection,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(void, AddConnection, (const cricket::Connection*), (override));
+  MOCK_METHOD(void,
+              OnConnectionDestroyed,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(rtc::ArrayView<const cricket::Connection*>,
+              connections,
+              (),
+              (const, override));
+  MOCK_METHOD(bool, HasPingableConnection, (), (const, override));
+  MOCK_METHOD(cricket::IceControllerInterface::PingResult,
+              SelectConnectionToPing,
+              (int64_t),
+              (override));
+  MOCK_METHOD(bool,
+              GetUseCandidateAttr,
+              (const cricket::Connection*,
+               cricket::NominationMode,
+               cricket::IceMode),
+              (const, override));
+  MOCK_METHOD(const cricket::Connection*,
+              FindNextPingableConnection,
+              (),
+              (override));
+  MOCK_METHOD(void,
+              MarkConnectionPinged,
+              (const cricket::Connection*),
+              (override));
+  MOCK_METHOD(cricket::IceControllerInterface::SwitchResult,
+              ShouldSwitchConnection,
+              (cricket::IceSwitchReason, const cricket::Connection*),
+              (override));
+  MOCK_METHOD(cricket::IceControllerInterface::SwitchResult,
+              SortAndSwitchConnection,
+              (cricket::IceSwitchReason),
+              (override));
+  MOCK_METHOD(std::vector<const cricket::Connection*>,
+              PruneConnections,
+              (),
+              (override));
+};
+
+class MockIceControllerFactory : public cricket::IceControllerFactoryInterface {
+ public:
+  ~MockIceControllerFactory() override = default;
+
+  std::unique_ptr<cricket::IceControllerInterface> Create(
+      const cricket::IceControllerFactoryArgs& args) override {
+    RecordIceControllerCreated();
+    return std::make_unique<MockIceController>(args);
+  }
+
+  MOCK_METHOD(void, RecordIceControllerCreated, ());
+};
+
+}  // namespace cricket
+
+#endif  // P2P_BASE_MOCK_ICE_CONTROLLER_H_
diff --git a/p2p/base/p2p_transport_channel.cc b/p2p/base/p2p_transport_channel.cc
index 6cc82b8..2b0e906 100644
--- a/p2p/base/p2p_transport_channel.cc
+++ b/p2p/base/p2p_transport_channel.cc
@@ -93,15 +93,23 @@
                             uses_turn);
 }
 
-}  // unnamed namespace
-
-namespace cricket {
+bool UseActiveIceControllerFieldTrialEnabled(
+    const webrtc::FieldTrialsView* field_trials) {
+  // Feature to refactor ICE controller and enable active ICE controllers.
+  // Field trial key reserved in bugs.webrtc.org/14367
+  return field_trials &&
+         field_trials->IsEnabled("WebRTC-UseActiveIceController");
+}
 
 using ::webrtc::RTCError;
 using ::webrtc::RTCErrorType;
 using ::webrtc::SafeTask;
 using ::webrtc::TimeDelta;
 
+}  // unnamed namespace
+
+namespace cricket {
+
 bool IceCredentialsChanged(absl::string_view old_ufrag,
                            absl::string_view old_pwd,
                            absl::string_view new_ufrag,
@@ -122,12 +130,14 @@
         transport_name, component, init.port_allocator(), nullptr,
         std::make_unique<webrtc::WrappingAsyncDnsResolverFactory>(
             init.async_resolver_factory()),
-        init.event_log(), init.ice_controller_factory(), init.field_trials()));
+        init.event_log(), init.ice_controller_factory(),
+        init.active_ice_controller_factory(), init.field_trials()));
   } else {
     return absl::WrapUnique(new P2PTransportChannel(
         transport_name, component, init.port_allocator(),
         init.async_dns_resolver_factory(), nullptr, init.event_log(),
-        init.ice_controller_factory(), init.field_trials()));
+        init.ice_controller_factory(), init.active_ice_controller_factory(),
+        init.field_trials()));
   }
 }
 
@@ -143,6 +153,7 @@
                           /* owned_dns_resolver_factory= */ nullptr,
                           /* event_log= */ nullptr,
                           /* ice_controller_factory= */ nullptr,
+                          /* active_ice_controller_factory= */ nullptr,
                           field_trials) {}
 
 // Private constructor, called from Create()
@@ -155,6 +166,7 @@
         owned_dns_resolver_factory,
     webrtc::RtcEventLog* event_log,
     IceControllerFactoryInterface* ice_controller_factory,
+    ActiveIceControllerFactoryInterface* active_ice_controller_factory,
     const webrtc::FieldTrialsView* field_trials)
     : transport_name_(transport_name),
       component_(component),
@@ -210,11 +222,9 @@
       &ice_field_trials_,
       field_trials ? field_trials->Lookup("WebRTC-IceControllerFieldTrials")
                    : ""};
-  if (ice_controller_factory != nullptr) {
-    ice_controller_ = ice_controller_factory->Create(args);
-  } else {
-    ice_controller_ = std::make_unique<BasicIceController>(args);
-  }
+  ice_adapter_ = std::make_unique<IceControllerAdapter>(
+      args, ice_controller_factory, active_ice_controller_factory, field_trials,
+      /* transport= */ this);
 }
 
 P2PTransportChannel::~P2PTransportChannel() {
@@ -282,18 +292,21 @@
                          webrtc::IceCandidatePairConfigType::kAdded);
 
   connections_.push_back(connection);
-  ice_controller_->AddConnection(connection);
+  ice_adapter_->OnConnectionAdded(connection);
 }
 
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
 bool P2PTransportChannel::MaybeSwitchSelectedConnection(
-    Connection* new_connection,
+    const Connection* new_connection,
     IceSwitchReason reason) {
   RTC_DCHECK_RUN_ON(network_thread_);
 
   return MaybeSwitchSelectedConnection(
-      reason, ice_controller_->ShouldSwitchConnection(reason, new_connection));
+      reason,
+      ice_adapter_->LegacyShouldSwitchConnection(reason, new_connection));
 }
 
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
 bool P2PTransportChannel::MaybeSwitchSelectedConnection(
     IceSwitchReason reason,
     IceControllerInterface::SwitchResult result) {
@@ -530,7 +543,7 @@
         ice_params, static_cast<int>(remote_ice_parameters_.size() - 1));
   }
   // Updating the remote ICE candidate generation could change the sort order.
-  RequestSortAndStateUpdate(
+  ice_adapter_->OnSortAndSwitchRequest(
       IceSwitchReason::REMOTE_CANDIDATE_GENERATION_CHANGE);
 }
 
@@ -685,7 +698,8 @@
 
   if (config_.network_preference != config.network_preference) {
     config_.network_preference = config.network_preference;
-    RequestSortAndStateUpdate(IceSwitchReason::NETWORK_PREFERENCE_CHANGE);
+    ice_adapter_->OnSortAndSwitchRequest(
+        IceSwitchReason::NETWORK_PREFERENCE_CHANGE);
     RTC_LOG(LS_INFO) << "Set network preference to "
                      << (config_.network_preference.has_value()
                              ? config_.network_preference.value()
@@ -711,7 +725,7 @@
   config_.vpn_preference = config.vpn_preference;
   allocator_->SetVpnPreference(config_.vpn_preference);
 
-  ice_controller_->SetIceConfig(config_);
+  ice_adapter_->SetIceConfig(config_);
 
   RTC_DCHECK(ValidateIceConfig(config_).ok());
 }
@@ -988,7 +1002,7 @@
     CreateConnection(port, *iter, iter->origin_port());
   }
 
-  SortConnectionsAndUpdateState(
+  ice_adapter_->OnImmediateSortAndSwitchRequest(
       IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE);
 }
 
@@ -1168,7 +1182,7 @@
   // Update the list of connections since we just added another.  We do this
   // after sending the response since it could (in principle) delete the
   // connection in question.
-  SortConnectionsAndUpdateState(
+  ice_adapter_->OnImmediateSortAndSwitchRequest(
       IceSwitchReason::NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS);
 }
 
@@ -1219,11 +1233,12 @@
 
   // TODO(qingsi): RequestSortAndStateUpdate will eventually call
   // MaybeSwitchSelectedConnection again. Rewrite this logic.
-  if (MaybeSwitchSelectedConnection(
-          conn, IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE)) {
+  if (ice_adapter_->OnImmediateSwitchRequest(
+          IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE, conn)) {
     // Now that we have selected a connection, it is time to prune other
     // connections and update the read/write state of the channel.
-    RequestSortAndStateUpdate(IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE);
+    ice_adapter_->OnSortAndSwitchRequest(
+        IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE);
   } else {
     RTC_LOG(LS_INFO)
         << "Not switching the selected connection on controlled side yet: "
@@ -1371,7 +1386,7 @@
   CreateConnections(new_remote_candidate, NULL);
 
   // Resort the connections list, which may have new elements.
-  SortConnectionsAndUpdateState(
+  ice_adapter_->OnImmediateSortAndSwitchRequest(
       IceSwitchReason::NEW_CONNECTION_FROM_REMOTE_CANDIDATE);
 }
 
@@ -1691,9 +1706,7 @@
 
 rtc::ArrayView<Connection*> P2PTransportChannel::connections() const {
   RTC_DCHECK_RUN_ON(network_thread_);
-  rtc::ArrayView<const Connection*> res = ice_controller_->connections();
-  return rtc::ArrayView<Connection*>(const_cast<Connection**>(res.data()),
-                                     res.size());
+  return ice_adapter_->LegacyConnections();
 }
 
 void P2PTransportChannel::RemoveConnectionForTest(Connection* connection) {
@@ -1724,6 +1737,7 @@
 }
 
 // Prepare for best candidate sorting.
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
 void P2PTransportChannel::RequestSortAndStateUpdate(
     IceSwitchReason reason_to_sort) {
   RTC_DCHECK_RUN_ON(network_thread_);
@@ -1736,13 +1750,14 @@
   }
 }
 
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
 void P2PTransportChannel::MaybeStartPinging() {
   RTC_DCHECK_RUN_ON(network_thread_);
   if (started_pinging_) {
     return;
   }
 
-  if (ice_controller_->HasPingableConnection()) {
+  if (ice_adapter_->LegacyHasPingableConnection()) {
     RTC_LOG(LS_INFO) << ToString()
                      << ": Have a pingable connection for the first time; "
                         "starting to ping.";
@@ -1782,6 +1797,7 @@
 
 // Sort the available connections to find the best one.  We also monitor
 // the number of available connections and the current state.
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
 void P2PTransportChannel::SortConnectionsAndUpdateState(
     IceSwitchReason reason_to_sort) {
   RTC_DCHECK_RUN_ON(network_thread_);
@@ -1797,7 +1813,8 @@
   // have to be writable to become the selected connection although it will
   // have higher priority if it is writable.
   MaybeSwitchSelectedConnection(
-      reason_to_sort, ice_controller_->SortAndSwitchConnection(reason_to_sort));
+      reason_to_sort,
+      ice_adapter_->LegacySortAndSwitchConnection(reason_to_sort));
 
   // The controlled side can prune only if the selected connection has been
   // nominated because otherwise it may prune the connection that will be
@@ -1865,7 +1882,7 @@
 void P2PTransportChannel::PruneConnections() {
   RTC_DCHECK_RUN_ON(network_thread_);
   std::vector<const Connection*> connections_to_prune =
-      ice_controller_->PruneConnections();
+      ice_adapter_->LegacyPruneConnections();
   PruneConnections(connections_to_prune);
 }
 
@@ -1976,7 +1993,7 @@
 
   ++selected_candidate_pair_changes_;
 
-  ice_controller_->SetSelectedConnection(selected_connection_);
+  ice_adapter_->OnConnectionSwitched(selected_connection_);
 }
 
 int64_t P2PTransportChannel::ComputeEstimatedDisconnectedTimeMs(
@@ -1990,11 +2007,11 @@
 }
 
 // Warning: UpdateTransportState should eventually be called whenever a
-// connection is added, deleted, or the write state of any connection changes
-// so that the transport controller will get the up-to-date channel state.
-// However it should not be called too often; in the case that multiple
-// connection states change, it should be called after all the connection
-// states have changed. For example, we call this at the end of
+// connection is added, deleted, or the write state of any connection changes so
+// that the transport controller will get the up-to-date channel state. However
+// it should not be called too often; in the case that multiple connection
+// states change, it should be called after all the connection states have
+// changed. For example, we call this at the end of
 // SortConnectionsAndUpdateState.
 void P2PTransportChannel::UpdateTransportState() {
   RTC_DCHECK_RUN_ON(network_thread_);
@@ -2091,7 +2108,7 @@
   RTC_LOG(LS_INFO) << "Selected connection destroyed. Will choose a new one.";
   IceSwitchReason reason = IceSwitchReason::SELECTED_CONNECTION_DESTROYED;
   SwitchSelectedConnectionInternal(nullptr, reason);
-  RequestSortAndStateUpdate(reason);
+  ice_adapter_->OnSortAndSwitchRequest(reason);
 }
 
 // If all connections timed out, delete them all.
@@ -2125,13 +2142,14 @@
 }
 
 // Handle queued up check-and-ping request
+// TODO(bugs.webrtc.org/14367) remove once refactor lands.
 void P2PTransportChannel::CheckAndPing() {
   RTC_DCHECK_RUN_ON(network_thread_);
   // Make sure the states of the connections are up-to-date (since this
   // affects which ones are pingable).
   UpdateConnectionStates();
 
-  auto result = ice_controller_->SelectConnectionToPing(last_ping_sent_ms_);
+  auto result = ice_adapter_->LegacySelectConnectionToPing(last_ping_sent_ms_);
   TimeDelta delay = TimeDelta::Millis(result.recheck_delay_ms);
 
   if (result.connection.value_or(nullptr)) {
@@ -2145,7 +2163,7 @@
 // This method is only for unit testing.
 Connection* P2PTransportChannel::FindNextPingableConnection() {
   RTC_DCHECK_RUN_ON(network_thread_);
-  auto* conn = ice_controller_->FindNextPingableConnection();
+  const Connection* conn = ice_adapter_->FindNextPingableConnection();
   if (conn) {
     return FromIceController(conn);
   } else {
@@ -2174,7 +2192,7 @@
 // active.
 void P2PTransportChannel::MarkConnectionPinged(Connection* conn) {
   RTC_DCHECK_RUN_ON(network_thread_);
-  ice_controller_->MarkConnectionPinged(conn);
+  ice_adapter_->OnConnectionPinged(conn);
 }
 
 // Apart from sending ping from `conn` this method also updates
@@ -2208,7 +2226,7 @@
 // Nominate a connection based on the NominationMode.
 bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn) const {
   RTC_DCHECK_RUN_ON(network_thread_);
-  return ice_controller_->GetUseCandidateAttr(
+  return ice_adapter_->GetUseCandidateAttribute(
       conn, config_.default_nomination_mode, remote_ice_mode_);
 }
 
@@ -2233,7 +2251,7 @@
   }
   // We have to unroll the stack before doing this because we may be changing
   // the state of connections while sorting.
-  RequestSortAndStateUpdate(
+  ice_adapter_->OnSortAndSwitchRequest(
       IceSwitchReason::CONNECT_STATE_CHANGE);  // "candidate pair state
                                                // changed");
 }
@@ -2261,9 +2279,9 @@
   if (selected_connection_ == connection) {
     OnSelectedConnectionDestroyed();
   } else {
-    // If a non-selected connection was destroyed, we don't need to re-sort
-    // but we do need to update state, because we could be switching to
-    // "failed" or "completed".
+    // If a non-selected connection was destroyed, we don't need to re-sort but
+    // we do need to update state, because we could be switching to "failed" or
+    // "completed".
     UpdateTransportState();
   }
 }
@@ -2273,7 +2291,7 @@
   auto it = absl::c_find(connections_, connection);
   RTC_DCHECK(it != connections_.end());
   connections_.erase(it);
-  ice_controller_->OnConnectionDestroyed(connection);
+  ice_adapter_->OnConnectionDestroyed(connection);
 }
 
 // When a port is destroyed, remove it from our list of ports to use for
@@ -2374,7 +2392,8 @@
   // May need to switch the sending connection based on the receiving media
   // path if this is the controlled side.
   if (ice_role_ == ICEROLE_CONTROLLED) {
-    MaybeSwitchSelectedConnection(connection, IceSwitchReason::DATA_RECEIVED);
+    ice_adapter_->OnImmediateSwitchRequest(IceSwitchReason::DATA_RECEIVED,
+                                           connection);
   }
 }
 
@@ -2445,4 +2464,167 @@
                                         conn->ToLogDescription());
 }
 
+P2PTransportChannel::IceControllerAdapter::IceControllerAdapter(
+    const IceControllerFactoryArgs& args,
+    IceControllerFactoryInterface* ice_controller_factory,
+    ActiveIceControllerFactoryInterface* active_ice_controller_factory,
+    const webrtc::FieldTrialsView* field_trials,
+    P2PTransportChannel* transport)
+    : transport_(transport) {
+  if (UseActiveIceControllerFieldTrialEnabled(field_trials)) {
+    RTC_DCHECK(active_ice_controller_factory);
+    ActiveIceControllerFactoryArgs active_args{args,
+                                               /* ice_agent= */ transport};
+    active_ice_controller_ = active_ice_controller_factory->Create(active_args);
+  } else {
+    if (ice_controller_factory != nullptr) {
+      legacy_ice_controller_ = ice_controller_factory->Create(args);
+    } else {
+      legacy_ice_controller_ = std::make_unique<BasicIceController>(args);
+    }
+  }
+}
+
+P2PTransportChannel::IceControllerAdapter::~IceControllerAdapter() = default;
+
+void P2PTransportChannel::IceControllerAdapter::SetIceConfig(
+    const IceConfig& config) {
+  active_ice_controller_ ? active_ice_controller_->SetIceConfig(config)
+                         : legacy_ice_controller_->SetIceConfig(config);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionAdded(
+    const Connection* connection) {
+  active_ice_controller_ ? active_ice_controller_->OnConnectionAdded(connection)
+                         : legacy_ice_controller_->AddConnection(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionSwitched(
+    const Connection* connection) {
+  active_ice_controller_
+      ? active_ice_controller_->OnConnectionSwitched(connection)
+      : legacy_ice_controller_->SetSelectedConnection(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionPinged(
+    const Connection* connection) {
+  active_ice_controller_
+      ? active_ice_controller_->OnConnectionPinged(connection)
+      : legacy_ice_controller_->MarkConnectionPinged(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionDestroyed(
+    const Connection* connection) {
+  active_ice_controller_
+      ? active_ice_controller_->OnConnectionDestroyed(connection)
+      : legacy_ice_controller_->OnConnectionDestroyed(connection);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnConnectionUpdated(
+    const Connection* connection) {
+  if (active_ice_controller_) {
+    active_ice_controller_->OnConnectionUpdated(connection);
+    return;
+  }
+  RTC_DCHECK_NOTREACHED();
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnSortAndSwitchRequest(
+    IceSwitchReason reason) {
+  active_ice_controller_
+      ? active_ice_controller_->OnSortAndSwitchRequest(reason)
+      : transport_->RequestSortAndStateUpdate(reason);
+}
+
+void P2PTransportChannel::IceControllerAdapter::OnImmediateSortAndSwitchRequest(
+    IceSwitchReason reason) {
+  active_ice_controller_
+      ? active_ice_controller_->OnImmediateSortAndSwitchRequest(reason)
+      : transport_->SortConnectionsAndUpdateState(reason);
+}
+
+bool P2PTransportChannel::IceControllerAdapter::OnImmediateSwitchRequest(
+    IceSwitchReason reason,
+    const Connection* connection) {
+  return active_ice_controller_
+             ? active_ice_controller_->OnImmediateSwitchRequest(reason,
+                                                                connection)
+             : transport_->MaybeSwitchSelectedConnection(connection, reason);
+}
+
+bool P2PTransportChannel::IceControllerAdapter::GetUseCandidateAttribute(
+    const cricket::Connection* connection,
+    cricket::NominationMode mode,
+    cricket::IceMode remote_ice_mode) const {
+  return active_ice_controller_
+             ? active_ice_controller_->GetUseCandidateAttribute(
+                   connection, mode, remote_ice_mode)
+             : legacy_ice_controller_->GetUseCandidateAttr(connection, mode,
+                                                           remote_ice_mode);
+}
+
+const Connection*
+P2PTransportChannel::IceControllerAdapter::FindNextPingableConnection() {
+  return active_ice_controller_
+             ? active_ice_controller_->FindNextPingableConnection()
+             : legacy_ice_controller_->FindNextPingableConnection();
+}
+
+rtc::ArrayView<Connection*>
+P2PTransportChannel::IceControllerAdapter::LegacyConnections() const {
+  RTC_DCHECK_RUN_ON(transport_->network_thread_);
+  if (active_ice_controller_) {
+    return rtc::ArrayView<Connection*>(transport_->connections_.data(),
+                                       transport_->connections_.size());
+  }
+
+  rtc::ArrayView<const Connection*> res = legacy_ice_controller_->connections();
+  return rtc::ArrayView<Connection*>(const_cast<Connection**>(res.data()),
+                                     res.size());
+}
+
+bool P2PTransportChannel::IceControllerAdapter::LegacyHasPingableConnection()
+    const {
+  if (active_ice_controller_) {
+    RTC_DCHECK_NOTREACHED();
+  }
+  return legacy_ice_controller_->HasPingableConnection();
+}
+
+IceControllerInterface::PingResult
+P2PTransportChannel::IceControllerAdapter::LegacySelectConnectionToPing(
+    int64_t last_ping_sent_ms) {
+  if (active_ice_controller_) {
+    RTC_DCHECK_NOTREACHED();
+  }
+  return legacy_ice_controller_->SelectConnectionToPing(last_ping_sent_ms);
+}
+
+IceControllerInterface::SwitchResult
+P2PTransportChannel::IceControllerAdapter::LegacyShouldSwitchConnection(
+    IceSwitchReason reason,
+    const Connection* connection) {
+  if (active_ice_controller_) {
+    RTC_DCHECK_NOTREACHED();
+  }
+  return legacy_ice_controller_->ShouldSwitchConnection(reason, connection);
+}
+
+IceControllerInterface::SwitchResult
+P2PTransportChannel::IceControllerAdapter::LegacySortAndSwitchConnection(
+    IceSwitchReason reason) {
+  if (active_ice_controller_) {
+    RTC_DCHECK_NOTREACHED();
+  }
+  return legacy_ice_controller_->SortAndSwitchConnection(reason);
+}
+
+std::vector<const Connection*>
+P2PTransportChannel::IceControllerAdapter::LegacyPruneConnections() {
+  if (active_ice_controller_) {
+    RTC_DCHECK_NOTREACHED();
+  }
+  return legacy_ice_controller_->PruneConnections();
+}
+
 }  // namespace cricket
diff --git a/p2p/base/p2p_transport_channel.h b/p2p/base/p2p_transport_channel.h
index 5592a4b..d7bcd23 100644
--- a/p2p/base/p2p_transport_channel.h
+++ b/p2p/base/p2p_transport_channel.h
@@ -46,6 +46,7 @@
 #include "api/transport/stun.h"
 #include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h"
 #include "logging/rtc_event_log/ice_logger.h"
+#include "p2p/base/active_ice_controller_factory_interface.h"
 #include "p2p/base/basic_async_resolver_factory.h"
 #include "p2p/base/candidate_pair_interface.h"
 #include "p2p/base/connection.h"
@@ -265,7 +266,9 @@
           owned_dns_resolver_factory,
       webrtc::RtcEventLog* event_log,
       IceControllerFactoryInterface* ice_controller_factory,
+      ActiveIceControllerFactoryInterface* active_ice_controller_factory,
       const webrtc::FieldTrialsView* field_trials);
+
   bool IsGettingPorts() {
     RTC_DCHECK_RUN_ON(network_thread_);
     return allocator_session()->IsGettingPorts();
@@ -274,12 +277,15 @@
   // Returns true if it's possible to send packets on `connection`.
   bool ReadyToSend(const Connection* connection) const;
   bool PresumedWritable(const Connection* conn) const;
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   void RequestSortAndStateUpdate(IceSwitchReason reason_to_sort);
   // Start pinging if we haven't already started, and we now have a connection
   // that's pingable.
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   void MaybeStartPinging();
   void SendPingRequestInternal(Connection* connection);
 
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   void SortConnectionsAndUpdateState(IceSwitchReason reason_to_sort);
   rtc::NetworkRoute ConfigureNetworkRoute(const Connection* conn);
   void SwitchSelectedConnectionInternal(Connection* conn,
@@ -348,6 +354,7 @@
 
   void OnNominated(Connection* conn);
 
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   void CheckAndPing();
 
   void LogCandidatePairConfig(Connection* conn,
@@ -357,12 +364,15 @@
   bool GetUseCandidateAttr(Connection* conn) const;
 
   // Returns true if the new_connection is selected for transmission.
-  bool MaybeSwitchSelectedConnection(Connection* new_connection,
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
+  bool MaybeSwitchSelectedConnection(const Connection* new_connection,
                                      IceSwitchReason reason);
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   bool MaybeSwitchSelectedConnection(
       IceSwitchReason reason,
       IceControllerInterface::SwitchResult result);
   bool AllowedToPruneConnections() const;
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   void PruneConnections();
 
   // Returns the latest remote ICE parameters or nullptr if there are no remote
@@ -420,6 +430,7 @@
 
   void ParseFieldTrials(const webrtc::FieldTrialsView* field_trials);
 
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   webrtc::ScopedTaskSafety task_safety_;
   std::string transport_name_ RTC_GUARDED_BY(network_thread_);
   int component_ RTC_GUARDED_BY(network_thread_);
@@ -447,6 +458,7 @@
 
   std::vector<RemoteCandidate> remote_candidates_
       RTC_GUARDED_BY(network_thread_);
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   bool sort_dirty_ RTC_GUARDED_BY(
       network_thread_);  // indicates whether another sort is needed right now
   bool had_connection_ RTC_GUARDED_BY(network_thread_) =
@@ -473,6 +485,7 @@
   IceConfig config_ RTC_GUARDED_BY(network_thread_);
   int last_sent_packet_id_ RTC_GUARDED_BY(network_thread_) =
       -1;  // -1 indicates no packet was sent before.
+  // TODO(bugs.webrtc.org/14367) remove once refactor lands.
   bool started_pinging_ RTC_GUARDED_BY(network_thread_) = false;
   // The value put in the "nomination" attribute for the next nominated
   // connection. A zero-value indicates the connection will not be nominated.
@@ -486,7 +499,53 @@
       RTC_GUARDED_BY(network_thread_);
   webrtc::IceEventLog ice_event_log_ RTC_GUARDED_BY(network_thread_);
 
-  std::unique_ptr<IceControllerInterface> ice_controller_
+  // The adapter transparently delegates ICE controller interactions to either
+  // the legacy or the active ICE controller depending on field trials.
+  // TODO(bugs.webrtc.org/14367) replace with active ICE controller eventually.
+  class IceControllerAdapter : public ActiveIceControllerInterface {
+   public:
+    IceControllerAdapter(
+        const IceControllerFactoryArgs& args,
+        IceControllerFactoryInterface* ice_controller_factory,
+        ActiveIceControllerFactoryInterface* active_ice_controller_factory,
+        const webrtc::FieldTrialsView* field_trials,
+        P2PTransportChannel* transport);
+    ~IceControllerAdapter() override;
+
+    // ActiveIceControllerInterface overrides
+    void SetIceConfig(const IceConfig& config) override;
+    void OnConnectionAdded(const Connection* connection) override;
+    void OnConnectionSwitched(const Connection* connection) override;
+    void OnConnectionPinged(const Connection* connection) override;
+    void OnConnectionDestroyed(const Connection* connection) override;
+    void OnConnectionUpdated(const Connection* connection) override;
+    void OnSortAndSwitchRequest(IceSwitchReason reason) override;
+    void OnImmediateSortAndSwitchRequest(IceSwitchReason reason) override;
+    bool OnImmediateSwitchRequest(IceSwitchReason reason,
+                                  const Connection* connection) override;
+    bool GetUseCandidateAttribute(const Connection* connection,
+                                  NominationMode mode,
+                                  IceMode remote_ice_mode) const override;
+    const Connection* FindNextPingableConnection() override;
+
+    // Methods only available with legacy ICE controller.
+    rtc::ArrayView<Connection*> LegacyConnections() const;
+    bool LegacyHasPingableConnection() const;
+    IceControllerInterface::PingResult LegacySelectConnectionToPing(
+        int64_t last_ping_sent_ms);
+    IceControllerInterface::SwitchResult LegacyShouldSwitchConnection(
+        IceSwitchReason reason,
+        const Connection* connection);
+    IceControllerInterface::SwitchResult LegacySortAndSwitchConnection(
+        IceSwitchReason reason);
+    std::vector<const Connection*> LegacyPruneConnections();
+
+   private:
+    P2PTransportChannel* transport_;
+    std::unique_ptr<IceControllerInterface> legacy_ice_controller_;
+    std::unique_ptr<ActiveIceControllerInterface> active_ice_controller_;
+  };
+  std::unique_ptr<IceControllerAdapter> ice_adapter_
       RTC_GUARDED_BY(network_thread_);
 
   struct CandidateAndResolver final {
diff --git a/p2p/base/p2p_transport_channel_unittest.cc b/p2p/base/p2p_transport_channel_unittest.cc
index d3178f7..928e9b2 100644
--- a/p2p/base/p2p_transport_channel_unittest.cc
+++ b/p2p/base/p2p_transport_channel_unittest.cc
@@ -16,11 +16,15 @@
 
 #include "absl/strings/string_view.h"
 #include "api/test/mock_async_dns_resolver.h"
+#include "p2p/base/active_ice_controller_factory_interface.h"
+#include "p2p/base/active_ice_controller_interface.h"
 #include "p2p/base/basic_ice_controller.h"
 #include "p2p/base/connection.h"
 #include "p2p/base/fake_port_allocator.h"
 #include "p2p/base/ice_transport_internal.h"
+#include "p2p/base/mock_active_ice_controller.h"
 #include "p2p/base/mock_async_resolver.h"
+#include "p2p/base/mock_ice_controller.h"
 #include "p2p/base/packet_transport_internal.h"
 #include "p2p/base/test_stun_server.h"
 #include "p2p/base/test_turn_server.h"
@@ -56,7 +60,6 @@
 using ::testing::InSequence;
 using ::testing::InvokeArgument;
 using ::testing::InvokeWithoutArgs;
-using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::ReturnRef;
 using ::testing::SaveArg;
@@ -184,18 +187,6 @@
   return allocator.release();
 }
 
-class MockIceControllerFactory : public cricket::IceControllerFactoryInterface {
- public:
-  ~MockIceControllerFactory() override = default;
-  std::unique_ptr<cricket::IceControllerInterface> Create(
-      const cricket::IceControllerFactoryArgs& args) override {
-    RecordIceControllerCreated();
-    return std::make_unique<cricket::BasicIceController>(args);
-  }
-
-  MOCK_METHOD(void, RecordIceControllerCreated, ());
-};
-
 // An one-shot resolver factory with default return arguments.
 // Resolution is immediate, always succeeds, and returns nonsense.
 class ResolverFactoryFixture : public webrtc::MockAsyncDnsResolverFactory {
@@ -6123,6 +6114,25 @@
                                   /* component= */ 77, std::move(init));
 }
 
+TEST(P2PTransportChannel, InjectActiveIceController) {
+  std::unique_ptr<rtc::SocketServer> socket_server =
+      rtc::CreateDefaultSocketServer();
+  rtc::AutoSocketServerThread main_thread(socket_server.get());
+  rtc::BasicPacketSocketFactory packet_socket_factory(socket_server.get());
+  MockActiveIceControllerFactory factory;
+  FakePortAllocator pa(rtc::Thread::Current(), &packet_socket_factory);
+  webrtc::test::ScopedKeyValueConfig field_trials(
+      "WebRTC-UseActiveIceController/Enabled/");
+  EXPECT_CALL(factory, RecordActiveIceControllerCreated()).Times(1);
+  webrtc::IceTransportInit init;
+  init.set_port_allocator(&pa);
+  init.set_active_ice_controller_factory(&factory);
+  init.set_field_trials(&field_trials);
+  auto dummy =
+      P2PTransportChannel::Create("transport_name",
+                                  /* component= */ 77, std::move(init));
+}
+
 class ForgetLearnedStateController : public cricket::BasicIceController {
  public:
   explicit ForgetLearnedStateController(