[android] Allow to configure field trials per PeerConnectionFactory

Provide webrtc::Environment to create AudioProcessing and
AudioDeviceModule through java layer

Reland of https://webrtc-review.googlesource.com/c/src/+/388620

Bug: webrtc:369904700, webrtc:413413572
Change-Id: Id1ac4bb94a4a80d788a409a235c8ea96d568f170
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/390322
Reviewed-by: ZoƩ Lepaul <xalep@webrtc.org>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44595}
diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn
index d9ca1e0..5ce345f 100644
--- a/sdk/android/BUILD.gn
+++ b/sdk/android/BUILD.gn
@@ -117,6 +117,7 @@
       ":base_jni",
       ":builtin_audio_codecs_jni",
       ":default_video_codec_factory_jni",
+      ":environment_jni",
       ":java_audio_device_module_jni",
       ":peerconnection_jni",
       ":video_jni",
@@ -182,6 +183,15 @@
     ]
   }
 
+  rtc_android_library("environment_java") {
+    visibility = [ "*" ]
+    sources = [ "api/org/webrtc/Environment.java" ]
+    deps = [
+      ":generated_environment_jni_java",
+      "//third_party/androidx:androidx_annotation_annotation_java",
+    ]
+  }
+
   rtc_android_library("audio_api_java") {
     visibility = [ "*" ]
     sources = [
@@ -330,6 +340,7 @@
       ":base_java",
       ":builtin_audio_codecs_java",
       ":default_video_codec_factory_java",
+      ":environment_java",
       ":generated_base_jni_java",
       ":generated_peerconnection_jni_java",
 
@@ -616,6 +627,21 @@
     ]
   }
 
+  rtc_library("environment_jni") {
+    visibility = [ "*" ]
+    allow_poison = [ "environment_construction" ]
+    sources = [ "src/jni/environment.cc" ]
+    deps = [
+      ":generated_environment_jni",
+      ":native_api_jni",
+      "../../api:field_trials",
+      "../../api:field_trials_view",
+      "../../api/environment",
+      "../../api/environment:environment_factory",
+      "//third_party/jni_zero",
+    ]
+  }
+
   rtc_library("builtin_audio_codecs_jni") {
     visibility = [ "*" ]
     allow_poison = [ "audio_codecs" ]
@@ -778,6 +804,7 @@
 
     deps = [
       ":base_jni",
+      ":environment_jni",
       ":generated_external_classes_jni",
       ":generated_external_classes_jni",
       ":generated_peerconnection_jni",
@@ -805,6 +832,7 @@
       "../../api/audio:builtin_audio_processing_builder",
       "../../api/audio_codecs:audio_codecs_api",
       "../../api/crypto:options",
+      "../../api/environment",
       "../../api/neteq:neteq_api",
       "../../api/rtc_event_log:rtc_event_log_factory",
       "../../api/task_queue:default_task_queue_factory",
@@ -1319,6 +1347,12 @@
     jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h"
   }
 
+  generate_jni("generated_environment_jni") {
+    sources = [ "api/org/webrtc/Environment.java" ]
+    namespace = "webrtc::jni"
+    jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h"
+  }
+
   generate_jni("generated_metrics_jni") {
     sources = [ "api/org/webrtc/Metrics.java" ]
     namespace = "webrtc::jni"
diff --git a/sdk/android/api/org/webrtc/AudioProcessingFactory.java b/sdk/android/api/org/webrtc/AudioProcessingFactory.java
index bd8fdb8..e0aa25b 100644
--- a/sdk/android/api/org/webrtc/AudioProcessingFactory.java
+++ b/sdk/android/api/org/webrtc/AudioProcessingFactory.java
@@ -16,5 +16,14 @@
    * Dynamically allocates a webrtc::AudioProcessing instance and returns a pointer to it.
    * The caller takes ownership of the object.
    */
-  public long createNative();
+  // TODO: bugs.webrtc.org/369904700 - Remove when implementations switch to
+  // another variant.
+  @Deprecated
+  public default long createNative() {
+    return 0;
+  }
+
+  public default long createNative(long webrtcEnvRef) {
+    return createNative();
+  }
 }
diff --git a/sdk/android/api/org/webrtc/Environment.java b/sdk/android/api/org/webrtc/Environment.java
new file mode 100644
index 0000000..10749ec
--- /dev/null
+++ b/sdk/android/api/org/webrtc/Environment.java
@@ -0,0 +1,54 @@
+/*
+ *  Copyright 2025 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.
+ */
+
+package org.webrtc;
+
+import androidx.annotation.Nullable;
+
+/** Wrapper for the webrtc::Environment native class. */
+public final class Environment implements AutoCloseable {
+  private final long webrtcEnv;
+
+  /** Builder for {@link Environment}. */
+  public static class Builder {
+    public Builder setFieldTrials(String fieldTrials) {
+      this.fieldTrials = fieldTrials;
+      return this;
+    }
+
+    public Environment build() {
+      return new Environment(this.fieldTrials);
+    }
+
+    private @Nullable String fieldTrials;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Returns non-owning non-null native pointer to the webrtc::Environment */
+  public long ref() {
+    return webrtcEnv;
+  }
+
+  @Override
+  public void close() {
+    nativeFree(webrtcEnv);
+  }
+
+  private Environment(@Nullable String fieldTrials) {
+    this.webrtcEnv = nativeCreate(fieldTrials);
+  }
+
+  private static native long nativeCreate(@Nullable String fieldTrials);
+
+  private static native void nativeFree(long webrtcEnv);
+}
diff --git a/sdk/android/api/org/webrtc/PeerConnectionFactory.java b/sdk/android/api/org/webrtc/PeerConnectionFactory.java
index 2b2ea15..eac3bb0 100644
--- a/sdk/android/api/org/webrtc/PeerConnectionFactory.java
+++ b/sdk/android/api/org/webrtc/PeerConnectionFactory.java
@@ -15,9 +15,6 @@
 import androidx.annotation.Nullable;
 import java.util.List;
 import org.webrtc.Logging.Severity;
-import org.webrtc.MediaStreamTrack;
-import org.webrtc.PeerConnection;
-import org.webrtc.RtpCapabilities;
 import org.webrtc.audio.AudioDeviceModule;
 import org.webrtc.audio.JavaAudioDeviceModule;
 
@@ -166,6 +163,7 @@
 
   public static class Builder {
     @Nullable private Options options;
+    private Environment.Builder envBuilder = Environment.builder();
     @Nullable private AudioDeviceModule audioDeviceModule;
     private AudioEncoderFactoryFactory audioEncoderFactoryFactory =
         new BuiltinAudioEncoderFactoryFactory();
@@ -186,6 +184,11 @@
       return this;
     }
 
+    public Builder setFieldTrials(String fieldTrials) {
+      envBuilder.setFieldTrials(fieldTrials);
+      return this;
+    }
+
     public Builder setAudioDeviceModule(AudioDeviceModule audioDeviceModule) {
       this.audioDeviceModule = audioDeviceModule;
       return this;
@@ -261,24 +264,30 @@
 
     public PeerConnectionFactory createPeerConnectionFactory() {
       checkInitializeHasBeenCalled();
-      if (audioDeviceModule == null) {
-        audioDeviceModule = JavaAudioDeviceModule.builder(ContextUtils.getApplicationContext())
-                                .createAudioDeviceModule();
+      try (Environment env = envBuilder.build()) {
+        if (audioDeviceModule == null) {
+          audioDeviceModule = JavaAudioDeviceModule.builder(ContextUtils.getApplicationContext())
+                                  .createAudioDeviceModule();
+        }
+        return nativeCreatePeerConnectionFactory(
+            ContextUtils.getApplicationContext(),
+            options,
+            env.ref(),
+            audioDeviceModule.getNative(env.ref()),
+            audioEncoderFactoryFactory.createNativeAudioEncoderFactory(),
+            audioDecoderFactoryFactory.createNativeAudioDecoderFactory(),
+            videoEncoderFactory,
+            videoDecoderFactory,
+            audioProcessingFactory == null ? 0 : audioProcessingFactory.createNative(env.ref()),
+            fecControllerFactoryFactory == null ? 0 : fecControllerFactoryFactory.createNative(),
+            networkControllerFactoryFactory == null
+                ? 0
+                : networkControllerFactoryFactory.createNativeNetworkControllerFactory(),
+            networkStatePredictorFactoryFactory == null
+                ? 0
+                : networkStatePredictorFactoryFactory.createNativeNetworkStatePredictorFactory(),
+            neteqFactoryFactory == null ? 0 : neteqFactoryFactory.createNativeNetEqFactory());
       }
-      return nativeCreatePeerConnectionFactory(ContextUtils.getApplicationContext(), options,
-          audioDeviceModule.getNativeAudioDeviceModulePointer(),
-          audioEncoderFactoryFactory.createNativeAudioEncoderFactory(),
-          audioDecoderFactoryFactory.createNativeAudioDecoderFactory(), videoEncoderFactory,
-          videoDecoderFactory,
-          audioProcessingFactory == null ? 0 : audioProcessingFactory.createNative(),
-          fecControllerFactoryFactory == null ? 0 : fecControllerFactoryFactory.createNative(),
-          networkControllerFactoryFactory == null
-              ? 0
-              : networkControllerFactoryFactory.createNativeNetworkControllerFactory(),
-          networkStatePredictorFactoryFactory == null
-              ? 0
-              : networkStatePredictorFactoryFactory.createNativeNetworkStatePredictorFactory(),
-          neteqFactoryFactory == null ? 0 : neteqFactoryFactory.createNativeNetEqFactory());
     }
   }
 
@@ -604,7 +613,7 @@
   private static native void nativeStopInternalTracingCapture();
 
   private static native PeerConnectionFactory nativeCreatePeerConnectionFactory(Context context,
-      Options options, long nativeAudioDeviceModule, long audioEncoderFactory,
+      Options options, long webrtcEnvRef, long nativeAudioDeviceModule, long audioEncoderFactory,
       long audioDecoderFactory, VideoEncoderFactory encoderFactory,
       VideoDecoderFactory decoderFactory, long nativeAudioProcessor,
       long nativeFecControllerFactory, long nativeNetworkControllerFactory,
diff --git a/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java b/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java
index 5a0bf5c..ba5097d 100644
--- a/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java
+++ b/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java
@@ -22,7 +22,14 @@
    * Returns a C++ pointer to a webrtc::AudioDeviceModule. Caller does _not_ take ownership and
    * lifetime is handled through the release() call.
    */
-  long getNativeAudioDeviceModulePointer();
+  @Deprecated
+  default long getNativeAudioDeviceModulePointer() {
+    return 0;
+  }
+
+  public default long getNative(long webrtcEnvRef) {
+    return getNativeAudioDeviceModulePointer();
+  }
 
   /**
    * Release resources for this AudioDeviceModule, including native resources. The object should not
diff --git a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java
index a28b9c2..1e36fef 100644
--- a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java
+++ b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java
@@ -62,4 +62,31 @@
                                          .createInitializationOptions());
     PeerConnectionFactory.shutdownInternalTracer();
   }
+
+  // Tests that the JNI glue between Java and C++ does not crash when creating a
+  // PeerConnectionFactory.
+  @Test
+  @SmallTest
+  public void testCreation() throws Exception {
+    PeerConnectionFactory.initialize(
+        PeerConnectionFactory.InitializationOptions.builder(
+                InstrumentationRegistry.getTargetContext())
+            .setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
+            .createInitializationOptions());
+
+    PeerConnectionFactory factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
+  }
+
+  @Test
+  @SmallTest
+  public void testCreationWithFieldTrials() throws Exception {
+    PeerConnectionFactory.initialize(
+        PeerConnectionFactory.InitializationOptions.builder(
+                InstrumentationRegistry.getTargetContext())
+            .setNativeLibraryName(TestConstants.NATIVE_LIBRARY)
+            .createInitializationOptions());
+
+    PeerConnectionFactory factory =
+        PeerConnectionFactory.builder().setFieldTrials("").createPeerConnectionFactory();
+  }
 }
diff --git a/sdk/android/src/jni/environment.cc b/sdk/android/src/jni/environment.cc
new file mode 100644
index 0000000..fab50eb
--- /dev/null
+++ b/sdk/android/src/jni/environment.cc
@@ -0,0 +1,42 @@
+/*
+ *  Copyright 2025 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 "api/environment/environment.h"
+
+#include <jni.h>
+
+#include <memory>
+#include <utility>
+
+#include "api/environment/environment_factory.h"
+#include "api/field_trials.h"
+#include "api/field_trials_view.h"
+#include "sdk/android/generated_environment_jni/Environment_jni.h"
+#include "sdk/android/native_api/jni/java_types.h"
+#include "third_party/jni_zero/jni_zero.h"
+
+namespace webrtc::jni {
+
+void JNI_Environment_Free(JNIEnv* /*env*/, jlong webrtcEnv) {
+  delete reinterpret_cast<Environment*>(webrtcEnv);
+}
+
+jlong JNI_Environment_Create(
+    JNIEnv* env,
+    const jni_zero::JavaParamRef<jstring>& fieldTrials) {
+  std::unique_ptr<FieldTrialsView> field_trials;
+  if (!IsNull(env, fieldTrials)) {
+    field_trials =
+        std::make_unique<FieldTrials>(JavaToNativeString(env, fieldTrials));
+  }
+  return NativeToJavaPointer(
+      new Environment(CreateEnvironment(std::move(field_trials))));
+}
+
+}  // namespace webrtc::jni
diff --git a/sdk/android/src/jni/pc/peer_connection_factory.cc b/sdk/android/src/jni/pc/peer_connection_factory.cc
index a09859a..fbab65f 100644
--- a/sdk/android/src/jni/pc/peer_connection_factory.cc
+++ b/sdk/android/src/jni/pc/peer_connection_factory.cc
@@ -29,6 +29,7 @@
 #include "api/audio_codecs/audio_encoder_factory.h"
 #include "api/audio_options.h"
 #include "api/enable_media.h"
+#include "api/environment/environment.h"
 #include "api/fec_controller.h"
 #include "api/media_stream_interface.h"
 #include "api/neteq/neteq_factory.h"
@@ -256,6 +257,7 @@
     JNIEnv* jni,
     const jni_zero::JavaParamRef<jobject>& jcontext,
     const jni_zero::JavaParamRef<jobject>& joptions,
+    const Environment& env,
     scoped_refptr<AudioDeviceModule> audio_device_module,
     scoped_refptr<AudioEncoderFactory> audio_encoder_factory,
     scoped_refptr<AudioDecoderFactory> audio_decoder_factory,
@@ -292,8 +294,8 @@
       JavaToNativePeerConnectionFactoryOptions(jni, joptions);
 
   PeerConnectionFactoryDependencies dependencies;
-  // TODO(bugs.webrtc.org/13145): Also add socket_server.get() to the
-  // dependencies.
+  dependencies.env = env;
+  dependencies.socket_factory = socket_server.get();
   dependencies.network_thread = network_thread.get();
   dependencies.worker_thread = worker_thread.get();
   dependencies.signaling_thread = signaling_thread.get();
@@ -347,6 +349,7 @@
     JNIEnv* jni,
     const jni_zero::JavaParamRef<jobject>& jcontext,
     const jni_zero::JavaParamRef<jobject>& joptions,
+    jlong webrtc_env_ref,
     jlong native_audio_device_module,
     jlong native_audio_encoder_factory,
     jlong native_audio_decoder_factory,
@@ -357,10 +360,12 @@
     jlong native_network_controller_factory,
     jlong native_network_state_predictor_factory,
     jlong native_neteq_factory) {
+  const Environment* env = reinterpret_cast<Environment*>(webrtc_env_ref);
+  RTC_CHECK(env != nullptr);
   scoped_refptr<AudioProcessing> audio_processor(
       reinterpret_cast<AudioProcessing*>(native_audio_processor));
   return CreatePeerConnectionFactoryForJava(
-      jni, jcontext, joptions,
+      jni, jcontext, joptions, *env,
       scoped_refptr<AudioDeviceModule>(
           reinterpret_cast<AudioDeviceModule*>(native_audio_device_module)),
       TakeOwnershipOfRefPtr<AudioEncoderFactory>(native_audio_encoder_factory),