Create a custom test launcher for android
Set use_default_launcher=false in rtc_test on android
Bug: webrtc:42223878
Change-Id: If05da40b420d5da8f9e0f39560eb07380ebada14
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/368921
Owners-Override: Jeremy Leconte <jleconte@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Jeremy Leconte <jleconte@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Jeremy Leconte <jleconte@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43505}
diff --git a/BUILD.gn b/BUILD.gn
index b3c84aa..d829c0f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -695,7 +695,6 @@
deps += [
"sdk/android:native_unittests",
"sdk/android:native_unittests_java",
- "//testing/android/native_test:native_test_support",
]
shard_timeout = 900
}
@@ -744,11 +743,7 @@
data = video_engine_tests_resources
if (is_android) {
use_default_launcher = false
- deps += [
- "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
- "//testing/android/native_test:native_test_java",
- "//testing/android/native_test:native_test_support",
- ]
+ deps += [ "//build/android/gtest_apk:native_test_instrumentation_test_runner_java" ]
shard_timeout = 900
}
if (is_ios) {
@@ -793,11 +788,7 @@
data = webrtc_perf_tests_resources
if (is_android) {
use_default_launcher = false
- deps += [
- "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
- "//testing/android/native_test:native_test_java",
- "//testing/android/native_test:native_test_support",
- ]
+ deps += [ "//build/android/gtest_apk:native_test_instrumentation_test_runner_java" ]
shard_timeout = 4500
}
if (is_ios) {
@@ -809,7 +800,6 @@
testonly = true
deps = [ "rtc_base:rtc_base_nonparallel_tests" ]
if (is_android) {
- deps += [ "//testing/android/native_test:native_test_support" ]
shard_timeout = 900
}
}
diff --git a/common_audio/BUILD.gn b/common_audio/BUILD.gn
index 1342eef..4845bc7 100644
--- a/common_audio/BUILD.gn
+++ b/common_audio/BUILD.gn
@@ -388,8 +388,6 @@
]
if (is_android) {
- deps += [ "//testing/android/native_test:native_test_support" ]
-
shard_timeout = 900
}
}
diff --git a/common_video/BUILD.gn b/common_video/BUILD.gn
index 4cff570..4c4c8c9 100644
--- a/common_video/BUILD.gn
+++ b/common_video/BUILD.gn
@@ -219,7 +219,6 @@
data = common_video_resources
if (is_android) {
- deps += [ "//testing/android/native_test:native_test_support" ]
shard_timeout = 900
}
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 1b8f4cc..7a27f40 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -1074,7 +1074,6 @@
data = rtc_media_unittests_resources
if (is_android) {
- deps += [ "//testing/android/native_test:native_test_support" ]
shard_timeout = 900
}
diff --git a/modules/BUILD.gn b/modules/BUILD.gn
index 52bd86f..76fcf08 100644
--- a/modules/BUILD.gn
+++ b/modules/BUILD.gn
@@ -83,8 +83,6 @@
# rtc_test targets. Therefore we include this target here, instead of
# in video_coding_modules_tests, where it is actually used.
"../sdk/android:libjingle_peerconnection_java",
- "//sdk/android:native_test_jni_onload",
- "//testing/android/native_test:native_test_support",
]
shard_timeout = 900
}
@@ -238,10 +236,7 @@
if (is_android) {
use_default_launcher = false
- deps += [
- "../sdk/android:libjingle_peerconnection_java",
- "//testing/android/native_test:native_test_support",
- ]
+ deps += [ "../sdk/android:libjingle_peerconnection_java" ]
shard_timeout = 900
}
if (is_ios) {
diff --git a/modules/audio_coding/BUILD.gn b/modules/audio_coding/BUILD.gn
index bdc4860..3511b8f 100644
--- a/modules/audio_coding/BUILD.gn
+++ b/modules/audio_coding/BUILD.gn
@@ -1003,11 +1003,7 @@
if (is_android) {
use_default_launcher = false
- deps += [
- "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
- "//testing/android/native_test:native_test_java",
- "//testing/android/native_test:native_test_support",
- ]
+ deps += [ "//build/android/gtest_apk:native_test_instrumentation_test_runner_java" ]
shard_timeout = 900
}
if (is_ios) {
@@ -1099,11 +1095,7 @@
if (is_android) {
use_default_launcher = false
- deps += [
- "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
- "//testing/android/native_test:native_test_java",
- "//testing/android/native_test:native_test_support",
- ]
+ deps += [ "//build/android/gtest_apk:native_test_instrumentation_test_runner_java" ]
shard_timeout = 900
}
diff --git a/modules/audio_device/BUILD.gn b/modules/audio_device/BUILD.gn
index b69ada7..aa4d216 100644
--- a/modules/audio_device/BUILD.gn
+++ b/modules/audio_device/BUILD.gn
@@ -493,7 +493,6 @@
"../../sdk/android:internal_jni",
"../../sdk/android:libjingle_peerconnection_java",
"../../sdk/android:native_api_jni",
- "../../sdk/android:native_test_jni_onload",
"../utility",
]
}
diff --git a/modules/video_coding/BUILD.gn b/modules/video_coding/BUILD.gn
index c5c6ce1..2318bcd 100644
--- a/modules/video_coding/BUILD.gn
+++ b/modules/video_coding/BUILD.gn
@@ -1000,8 +1000,6 @@
deps += [
":android_codec_factory_helper",
"../../sdk/android:hwcodecs_java",
- "//sdk/android:native_test_jni_onload",
- "//testing/android/native_test:native_test_support",
]
shard_timeout = 900
}
diff --git a/modules/video_coding/DEPS b/modules/video_coding/DEPS
index d62707c..49c640b 100644
--- a/modules/video_coding/DEPS
+++ b/modules/video_coding/DEPS
@@ -11,9 +11,6 @@
]
specific_include_rules = {
- "android_codec_factory_helper\.cc": [
- "+base/android",
- ],
"multiplex_encoder_adapter\.cc": [
"+media/base",
],
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 7193349..c39cf40 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -2098,11 +2098,7 @@
if (is_android) {
use_default_launcher = false
- deps += [
- "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
- "//testing/android/native_test:native_test_java",
- "//testing/android/native_test:native_test_support",
- ]
+ deps += [ "//build/android/gtest_apk:native_test_instrumentation_test_runner_java" ]
}
}
@@ -2448,8 +2444,7 @@
# We need to depend on this one directly, or classloads will fail for
# the voice engine BuildInfo, for instance.
- "//sdk/android:libjingle_peerconnection_java",
- "//sdk/android:native_test_jni_onload",
+ "../sdk/android:libjingle_peerconnection_java",
]
shard_timeout = 900
}
@@ -2519,12 +2514,11 @@
"test/android_test_initializer.h",
]
deps = [
+ "../modules/utility:utility",
+ "../rtc_base:checks",
"../rtc_base:ssl_adapter",
"../sdk/android:internal_jni",
"../sdk/android:libjingle_peerconnection_jni",
- "//modules/utility:utility",
- "//rtc_base:checks",
- "//testing/android/native_test:native_test_support",
]
}
}
@@ -2818,7 +2812,6 @@
sources = [ "test/svc_e2e_tests.cc" ]
data = svc_tests_resources
deps = [
- "..//test/network:simulated_network",
"../api:create_network_emulation_manager",
"../api:create_peer_connection_quality_test_frame_generator",
"../api:create_peerconnection_quality_test_fixture",
@@ -2843,6 +2836,7 @@
"../test:fileutils",
"../test:test_main",
"../test:test_support",
+ "../test/network:simulated_network",
"../test/pc/e2e:network_quality_metrics_reporter",
"../test/pc/e2e/analyzer/video:default_video_quality_analyzer",
]
diff --git a/pc/DEPS b/pc/DEPS
index 36439a3..227cf27 100644
--- a/pc/DEPS
+++ b/pc/DEPS
@@ -18,9 +18,6 @@
]
specific_include_rules = {
- "androidtestinitializer\.cc": [
- "+base/android", # Allowed only for Android tests.
- ],
"srtpfilter_unittest\.cc": [
"+crypto",
],
diff --git a/rtc_tools/BUILD.gn b/rtc_tools/BUILD.gn
index 68521ba..8f8e355 100644
--- a/rtc_tools/BUILD.gn
+++ b/rtc_tools/BUILD.gn
@@ -614,7 +614,6 @@
data = tools_unittests_resources
if (is_android) {
- deps += [ "//testing/android/native_test:native_test_support" ]
shard_timeout = 900
}
if (is_ios) {
diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn
index a051da0..8295d2f 100644
--- a/sdk/android/BUILD.gn
+++ b/sdk/android/BUILD.gn
@@ -91,7 +91,7 @@
":surfaceviewrenderer_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -140,6 +140,7 @@
":native_api_jni",
":video_egl_jni",
"../../pc:libjingle_peerconnection",
+ "../../rtc_base:logging",
"../../rtc_base:ssl_adapter",
"//third_party/jni_zero",
]
@@ -169,11 +170,13 @@
"src/java/org/webrtc/WebRtcClassLoader.java",
]
+ srcjar_deps = [ "//sdk/android:generated_native_api_jni" ]
+
deps = [
":generated_base_jni_java",
":generated_native_api_jni_java",
":generated_rtcerror_jni_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
}
@@ -188,7 +191,7 @@
deps = [
":base_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -210,7 +213,7 @@
deps = [
":base_java",
":generated_video_jni_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
srcjar_deps = [ "//api/video:video_frame_enums" ]
@@ -258,7 +261,7 @@
":generated_video_egl_jni_java",
":generated_video_jni_java",
":video_api_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
}
@@ -335,7 +338,7 @@
":swcodecs_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
srcjar_deps = [
@@ -366,7 +369,7 @@
":base_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
}
@@ -398,7 +401,7 @@
":base_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -426,7 +429,7 @@
":base_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
}
@@ -450,7 +453,7 @@
":generated_audio_device_module_base_jni_java",
":generated_java_audio_device_module_native_jni_java",
":generated_java_audio_jni_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
}
@@ -475,7 +478,7 @@
deps = [
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
}
@@ -491,7 +494,7 @@
":base_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -506,7 +509,7 @@
":generated_libvpx_vp8_jni_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -521,7 +524,7 @@
":generated_libvpx_vp9_jni_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -533,7 +536,7 @@
":generated_libaom_av1_encoder_jni_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -561,7 +564,7 @@
":libvpx_vp9_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]
}
@@ -970,9 +973,9 @@
":generated_external_classes_jni",
":generated_native_api_jni",
":internal_jni",
+ "../../api:array_view",
"../../api:sequence_checker",
- "//api:array_view",
- "//rtc_base:checks",
+ "../../rtc_base:checks",
"//third_party/jni_zero",
]
}
@@ -987,7 +990,7 @@
deps = [
":base_jni",
":native_api_jni",
- "//rtc_base:checks",
+ "../../rtc_base:checks",
]
}
@@ -1035,8 +1038,8 @@
":base_jni",
":native_api_jni",
":video_jni",
- "//api/video_codecs:video_codecs_api",
- "//rtc_base:checks",
+ "../../api/video_codecs:video_codecs_api",
+ "../../rtc_base:checks",
"//third_party/jni_zero",
]
}
@@ -1067,9 +1070,9 @@
deps = [
":base_jni",
":peerconnection_jni",
+ "../../api:libjingle_peerconnection_api",
+ "../../api/video_codecs:video_codecs_api",
"../../rtc_base:threading",
- "//api:libjingle_peerconnection_api",
- "//api/video_codecs:video_codecs_api",
]
}
@@ -1105,12 +1108,12 @@
":native_api_jni",
":video_jni",
":videoframe_jni",
+ "../../api:libjingle_peerconnection_api",
+ "../../api:media_stream_interface",
+ "../../api/video:video_frame",
+ "../../api/video:video_rtp_headers",
"../../rtc_base:refcount",
"../../rtc_base:threading",
- "//api:libjingle_peerconnection_api",
- "//api:media_stream_interface",
- "//api/video:video_frame",
- "//api/video:video_rtp_headers",
"//third_party/jni_zero",
]
}
@@ -1125,7 +1128,7 @@
deps = [
":base_java",
":generated_logging_jni_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
]
}
@@ -1571,7 +1574,7 @@
":swcodecs_java",
":video_api_java",
":video_java",
- "//rtc_base:base_java",
+ "../../rtc_base:base_java",
"//third_party/android_deps:guava_android_java",
"//third_party/androidx:androidx_annotation_annotation_java",
"//third_party/androidx:androidx_test_monitor_java",
@@ -1603,6 +1606,7 @@
":libjingle_peerconnection_metrics_default_jni",
":native_api_jni",
"../../pc:libjingle_peerconnection",
+ "../../rtc_base:logging",
"../../rtc_base:ssl_adapter",
]
output_extension = "so"
@@ -1631,11 +1635,13 @@
sources = [ "native_unittests/test_jni_onload.cc" ]
deps = [
+ ":base_java",
":base_jni",
":internal_jni",
":native_api_base",
":native_api_jni",
"../../rtc_base:checks",
+ "../../rtc_base:logging",
]
}
@@ -1668,7 +1674,6 @@
":native_api_peerconnection",
":native_api_stacktrace",
":native_api_video",
- ":native_test_jni_onload",
":opensles_audio_device_module",
":video_jni",
"../../api:enable_media_with_defaults",
diff --git a/sdk/android/native_unittests/test_jni_onload.cc b/sdk/android/native_unittests/test_jni_onload.cc
index dafe49c..91b2f2e 100644
--- a/sdk/android/native_unittests/test_jni_onload.cc
+++ b/sdk/android/native_unittests/test_jni_onload.cc
@@ -13,11 +13,21 @@
#define JNIEXPORT __attribute__((visibility("default")))
#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
#include "sdk/android/native_api/base/init.h"
#include "sdk/android/native_api/jni/java_types.h"
+#include "test/android/native_test_launcher.h" // nogncheck
// This is called by the VM when the shared library is first loaded.
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ RTC_LOG(LS_INFO) << "Entering JNI_OnLoad in test_jni_onload.cc";
+
+ jni_zero::InitVM(vm);
+ // TODO(webrtc:42223878): Set exception handler?
+ // jni_zero::SetExceptionHandler(CheckException);
+ // JNIEnv* env = jni_zero::AttachCurrentThread();
+ // TODO(webrtc:42223878): Classloader, OOM error handler?
webrtc::InitAndroid(vm);
+ webrtc::test::android::InstallHandlers();
return JNI_VERSION_1_4;
}
diff --git a/sdk/android/src/jni/audio_device/DEPS b/sdk/android/src/jni/audio_device/DEPS
index 9a3adee..7ee1155 100644
--- a/sdk/android/src/jni/audio_device/DEPS
+++ b/sdk/android/src/jni/audio_device/DEPS
@@ -1,4 +1,3 @@
include_rules = [
- "+base/android/jni_android.h",
"+modules/audio_device",
]
diff --git a/sdk/android/src/jni/jni_onload.cc b/sdk/android/src/jni/jni_onload.cc
index a1829ad..d23cd92 100644
--- a/sdk/android/src/jni/jni_onload.cc
+++ b/sdk/android/src/jni/jni_onload.cc
@@ -12,6 +12,7 @@
#undef JNIEXPORT
#define JNIEXPORT __attribute__((visibility("default")))
+#include "rtc_base/logging.h"
#include "rtc_base/ssl_adapter.h"
#include "sdk/android/native_api/jni/class_loader.h"
#include "sdk/android/src/jni/jni_helpers.h"
@@ -20,6 +21,7 @@
namespace jni {
extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM* jvm, void* reserved) {
+ RTC_LOG(LS_INFO) << "Entering JNI_OnLoad in jni_onload.cc";
jint ret = InitGlobalJniVariables(jvm);
RTC_DCHECK_GE(ret, 0);
if (ret < 0)
diff --git a/stats/BUILD.gn b/stats/BUILD.gn
index ab4be89..5a48d85 100644
--- a/stats/BUILD.gn
+++ b/stats/BUILD.gn
@@ -68,11 +68,7 @@
if (is_android) {
use_default_launcher = false
- deps += [
- "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
- "//testing/android/native_test:native_test_java",
- "//testing/android/native_test:native_test_support",
- ]
+ deps += [ "//build/android/gtest_apk:native_test_instrumentation_test_runner_java" ]
}
}
}
diff --git a/system_wrappers/BUILD.gn b/system_wrappers/BUILD.gn
index 1afd20a..786c469 100644
--- a/system_wrappers/BUILD.gn
+++ b/system_wrappers/BUILD.gn
@@ -154,8 +154,6 @@
]
if (is_android) {
- deps += [ "//testing/android/native_test:native_test_support" ]
-
shard_timeout = 900
}
}
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 09bfbf8..bfe3cc3 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -10,6 +10,7 @@
import("../webrtc.gni")
if (is_android) {
import("//build/config/android/rules.gni")
+ import("//third_party/jni_zero/jni_zero.gni")
}
if (!build_with_chromium) {
@@ -798,7 +799,6 @@
data = test_support_unittests_resources
if (is_android) {
- deps += [ "//testing/android/native_test:native_test_support" ]
shard_timeout = 900
}
@@ -1301,14 +1301,43 @@
rtc_android_library("native_test_java") {
testonly = true
sources = [
+ "android/org/webrtc/native_test/NativeTestWebrtc.java",
+ "android/org/webrtc/native_test/RTCNativeTestApplication.java",
"android/org/webrtc/native_test/RTCNativeUnitTest.java",
"android/org/webrtc/native_test/RTCNativeUnitTestActivity.java",
+ "android/org/webrtc/native_test/StrictModeContext.java",
+ ]
+ srcjar_deps = [ ":native_test_jni" ]
+ deps = [
+ ":native_test_support",
+ "../rtc_base:base_java",
+ "//build/android:build_java",
+ "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
+ "//testing/android/reporter:reporter_java",
+ "//third_party/jni_zero:jni_zero_java",
+ ]
+ }
+
+ source_set("native_test_support") {
+ testonly = true
+ sources = [
+ "android/native_test_launcher.cc",
+ "android/native_test_launcher.h",
+ "android/native_test_util.cc",
+ "android/native_test_util.h",
]
deps = [
- "../rtc_base:base_java",
- "//testing/android/native_test:native_test_java",
+ ":native_test_jni",
+ "//testing/gtest",
+ "//third_party/abseil-cpp/absl/strings",
+ "//third_party/jni_zero:jni_zero",
]
}
+
+ generate_jni("native_test_jni") {
+ testonly = true
+ sources = [ "android/org/webrtc/native_test/NativeTestWebrtc.java" ]
+ }
}
rtc_library("call_config_utils") {
diff --git a/test/DEPS b/test/DEPS
index 95193e7..497c79a 100644
--- a/test/DEPS
+++ b/test/DEPS
@@ -1,6 +1,8 @@
include_rules = [
"+third_party/libjpeg",
"+third_party/libjpeg_turbo",
+ "+third_party/jni_zero",
+ "+absl/strings/str_split.h",
"+call",
"+common_audio",
"+common_video",
diff --git a/test/android/AndroidManifest.xml b/test/android/AndroidManifest.xml
index 04ab33c..478e4dd 100644
--- a/test/android/AndroidManifest.xml
+++ b/test/android/AndroidManifest.xml
@@ -24,13 +24,18 @@
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/> <!-- From Chromium -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <!-- From Chromium -->
+ <uses-permission android:name="android.permission.SET_TIME_ZONE"/> <!-- From Chromium -->
<application android:label="NativeTests"
- android:name="org.chromium.native_test.NativeTestApplication">
+ android:name="org.webrtc.native_test.RTCNativeTestApplication">
<uses-library android:name="android.test.runner"/>
<activity android:name=".RTCNativeUnitTestActivity"
android:label="NativeTest"
android:configChanges="orientation|keyboardHidden"
+ android:requestLegacyExternalStorage="true"
+ android:memtagMode="sync"
android:process=":test_process">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/test/android/native_test_launcher.cc b/test/android/native_test_launcher.cc
new file mode 100644
index 0000000..89135a0
--- /dev/null
+++ b/test/android/native_test_launcher.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+// Based on Chromium's
+// https://source.chromium.org/chromium/chromium/src/+/main:testing/android/native_test/native_test_launcher.cc
+// and Angle's
+// https://source.chromium.org/chromium/chromium/src/+/main:third_party/angle/src/tests/test_utils/runner/android/AngleNativeTest.cpp
+
+// This class sets up the environment for running the native tests inside an
+// android application. It outputs (to a fifo) markers identifying the
+// START/PASSED/CRASH of the test suite, FAILURE/SUCCESS of individual tests,
+// etc.
+// These markers are read by the test runner script to generate test results.
+// It installs signal handlers to detect crashes.
+
+#include "test/android/native_test_launcher.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include "test/android/native_test_util.h"
+#include "test/native_test_jni/NativeTestWebrtc_jni.h"
+#include "third_party/jni_zero/jni_zero.h"
+
+// The main function of the program to be wrapped as a test apk.
+extern int main(int argc, char** argv);
+
+namespace webrtc {
+namespace test {
+namespace android {
+
+namespace {
+
+const char kCrashedMarker[] = "[ CRASHED ]\n";
+
+// The list of signals which are considered to be crashes.
+const int kExceptionSignals[] = {SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1};
+
+struct sigaction g_old_sa[NSIG];
+
+// This function runs in a compromised context. It should not allocate memory.
+void SignalHandler(int sig, siginfo_t* info, void* reserved) {
+ // Output the crash marker.
+ write(STDOUT_FILENO, kCrashedMarker, sizeof(kCrashedMarker) - 1);
+ g_old_sa[sig].sa_sigaction(sig, info, reserved);
+}
+
+} // namespace
+
+static void JNI_NativeTestWebrtc_RunTests(JNIEnv* env,
+ std::string& command_line_flags,
+ std::string& command_line_file_path,
+ std::string& stdout_file_path,
+ std::string& test_data_dir) {
+ AndroidLog(
+ ANDROID_LOG_INFO,
+ "Entering JNI_NativeTestWebrtc_RunTests with command_line_flags=%s, "
+ "command_line_file_path=%s, stdout_file_path=%s, test_data_dir=%s\n",
+ command_line_flags.c_str(), command_line_file_path.c_str(),
+ stdout_file_path.c_str(), test_data_dir.c_str());
+
+ // Required for DEATH_TESTS.
+ pthread_atfork(nullptr, nullptr, jni_zero::DisableJvmForTesting);
+
+ std::vector<std::string> args;
+
+ if (command_line_file_path.empty())
+ args.push_back("_");
+ else
+ ParseArgsFromCommandLineFile(command_line_file_path.c_str(), &args);
+
+ ParseArgsFromString(command_line_flags, &args);
+
+ std::vector<char*> argv;
+ int argc = ArgsToArgv(args, &argv);
+
+ // A few options, such "--gtest_list_tests", will just use printf directly
+ // Always redirect stdout to a known file.
+ if (freopen(stdout_file_path.c_str(), "a+", stdout) == NULL) {
+ AndroidLog(ANDROID_LOG_ERROR, "Failed to redirect stream to file: %s: %s\n",
+ stdout_file_path.c_str(), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ // TODO(jbudorick): Remove this after resolving crbug.com/726880
+ AndroidLog(ANDROID_LOG_INFO, "Redirecting stdout to file: %s\n",
+ stdout_file_path.c_str());
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+
+ // TODO(webrtc:42223878): Wait for debugger.
+
+ ScopedMainEntryLogger scoped_main_entry_logger;
+ main(argc, &argv[0]);
+}
+
+// TODO(nileshagrawal): now that we're using FIFO, test scripts can detect EOF.
+// Remove the signal handlers.
+void InstallHandlers() {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+
+ sa.sa_sigaction = SignalHandler;
+ sa.sa_flags = SA_SIGINFO;
+
+ for (unsigned int i = 0; kExceptionSignals[i] != -1; ++i) {
+ sigaction(kExceptionSignals[i], &sa, &g_old_sa[kExceptionSignals[i]]);
+ }
+}
+
+} // namespace android
+} // namespace test
+} // namespace webrtc
diff --git a/test/android/native_test_launcher.h b/test/android/native_test_launcher.h
new file mode 100644
index 0000000..75e4761
--- /dev/null
+++ b/test/android/native_test_launcher.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2024 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 TEST_ANDROID_NATIVE_TEST_LAUNCHER_H_
+#define TEST_ANDROID_NATIVE_TEST_LAUNCHER_H_
+
+#include <jni.h>
+
+namespace webrtc {
+namespace test {
+namespace android {
+
+void InstallHandlers();
+
+} // namespace android
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_ANDROID_NATIVE_TEST_LAUNCHER_H_
diff --git a/test/android/native_test_util.cc b/test/android/native_test_util.cc
new file mode 100644
index 0000000..529ffbe
--- /dev/null
+++ b/test/android/native_test_util.cc
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+// Based on Chromium's
+// https://source.chromium.org/chromium/chromium/src/+/main:testing/android/native_test/native_test_util.cc
+// and Angle's
+// https://source.chromium.org/chromium/chromium/src/+/main:third_party/angle/src/tests/test_utils/runner/android/AngleNativeTest.cpp
+
+#include "test/android/native_test_util.h"
+
+#include <android/log.h>
+#include <stdio.h>
+
+#include <optional>
+#include <string>
+
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+
+namespace webrtc {
+namespace test {
+namespace android {
+
+namespace {
+
+const char kLogTag[] = "webrtc";
+
+std::optional<std::string> ReadFileToString(const char* path) {
+ FILE* file = fopen(path, "rb");
+ if (!file) {
+ AndroidLog(ANDROID_LOG_ERROR, "Failed to open %s\n", path);
+ return std::nullopt;
+ }
+
+ if (fseek(file, 0, SEEK_END) != 0) {
+ return std::nullopt;
+ }
+
+ auto size = ftell(file);
+ // Check that `size` fits in a 32-bit int, to avoid any issues with overflow
+ // in the subsequent casts. (For example, consider the case where long is >32
+ // bits while size_t is 32 bits.) We're not expecting the command line to be
+ // larger than 1 GB, anyway.
+ if (size < 0 || size > 1'000'000'000) {
+ AndroidLog(ANDROID_LOG_ERROR,
+ "Expected size of %s between 0 and 1 GB, got %ld bytes\n", path,
+ size);
+ return std::nullopt;
+ }
+
+ std::string contents;
+ contents.resize(size);
+
+ fseek(file, 0, SEEK_SET);
+
+ if (fread(contents.data(), 1, size, file) != static_cast<size_t>(size)) {
+ return std::nullopt;
+ }
+
+ if (ferror(file)) {
+ return std::nullopt;
+ }
+
+ return contents;
+}
+
+} // namespace
+
+// Writes printf() style string to Android's logger where |priority| is one of
+// the levels defined in <android/log.h>.
+void AndroidLog(int priority, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ __android_log_vprint(priority, kLogTag, format, args);
+ va_end(args);
+}
+
+std::string ASCIIJavaStringToUTF8(JNIEnv* env, jstring str) {
+ if (!str) {
+ return "";
+ }
+
+ const jsize length = env->GetStringLength(str);
+ if (!length) {
+ return "";
+ }
+
+ // JNI's GetStringUTFChars() returns strings in Java "modified" UTF8, so
+ // instead get the String in UTF16. As the input is ASCII, drop the higher
+ // bytes.
+ const jchar* jchars = env->GetStringChars(str, NULL);
+ const char16_t* chars = reinterpret_cast<const char16_t*>(jchars);
+ std::string out(chars, chars + length);
+ env->ReleaseStringChars(str, jchars);
+ return out;
+}
+
+void ParseArgsFromString(const std::string& command_line,
+ std::vector<std::string>* args) {
+ std::vector<absl::string_view> v =
+ absl::StrSplit(command_line, absl::ByAsciiWhitespace());
+ for (absl::string_view arg : v) {
+ args->push_back(std::string(arg));
+ }
+
+ // TODO(webrtc:42223878): Implement tokenization that handle quotes and
+ // escaped quotes (along the lines of the previous chromium code):
+ //
+ // base::StringTokenizer tokenizer(command_line, base::kWhitespaceASCII);
+ // tokenizer.set_quote_chars("\"");
+ // while (tokenizer.GetNext()) {
+ // std::string token;
+ // base::RemoveChars(tokenizer.token(), "\"", &token);
+ // args->push_back(token);
+ // }
+}
+
+void ParseArgsFromCommandLineFile(const char* path,
+ std::vector<std::string>* args) {
+ std::optional<std::string> command_line_string = ReadFileToString(path);
+ if (command_line_string.has_value()) {
+ ParseArgsFromString(*command_line_string, args);
+ }
+}
+
+int ArgsToArgv(const std::vector<std::string>& args, std::vector<char*>* argv) {
+ // We need to pass in a non-const char**.
+ int argc = args.size();
+
+ argv->resize(argc + 1);
+ for (int i = 0; i < argc; ++i) {
+ (*argv)[i] = const_cast<char*>(args[i].c_str());
+ }
+ (*argv)[argc] = NULL; // argv must be NULL terminated.
+
+ return argc;
+}
+
+} // namespace android
+} // namespace test
+} // namespace webrtc
diff --git a/test/android/native_test_util.h b/test/android/native_test_util.h
new file mode 100644
index 0000000..ace8758
--- /dev/null
+++ b/test/android/native_test_util.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2024 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 TEST_ANDROID_NATIVE_TEST_UTIL_H_
+#define TEST_ANDROID_NATIVE_TEST_UTIL_H_
+
+#include <android/log.h>
+
+#include <string>
+#include <vector>
+
+#include "third_party/jni_zero/jni_zero.h"
+
+// Helper methods for setting up environment for running gtest tests
+// inside an APK.
+namespace webrtc {
+namespace test {
+namespace android {
+
+void AndroidLog(int priority, const char* format, ...);
+
+std::string ASCIIJavaStringToUTF8(JNIEnv* env, jstring str);
+
+void ParseArgsFromString(const std::string& command_line,
+ std::vector<std::string>* args);
+void ParseArgsFromCommandLineFile(const char* path,
+ std::vector<std::string>* args);
+int ArgsToArgv(const std::vector<std::string>& args, std::vector<char*>* argv);
+
+class ScopedMainEntryLogger {
+ public:
+ ScopedMainEntryLogger() {
+ AndroidLog(ANDROID_LOG_INFO, ">>ScopedMainEntryLogger\n");
+ }
+
+ ~ScopedMainEntryLogger() {
+ AndroidLog(ANDROID_LOG_INFO, "<<ScopedMainEntryLogger\n");
+ fflush(stdout);
+ fflush(stderr);
+ }
+};
+
+} // namespace android
+} // namespace test
+} // namespace webrtc
+
+namespace jni_zero {
+template <>
+inline std::string FromJniType<std::string>(JNIEnv* env,
+ const JavaRef<jobject>& input) {
+ return webrtc::test::android::ASCIIJavaStringToUTF8(
+ env, static_cast<jstring>(input.obj()));
+}
+} // namespace jni_zero
+
+#endif // TEST_ANDROID_NATIVE_TEST_UTIL_H_
diff --git a/test/android/org/webrtc/native_test/NativeTestWebrtc.java b/test/android/org/webrtc/native_test/NativeTestWebrtc.java
new file mode 100644
index 0000000..352f9c8
--- /dev/null
+++ b/test/android/org/webrtc/native_test/NativeTestWebrtc.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Based on Chromium's https://source.chromium.org/chromium/chromium/src/+/main:testing/android/native_test/java/src/org/chromium/native_test/NativeTest.java
+// and Angle's https://source.chromium.org/chromium/chromium/src/+/main:third_party/angle/src/tests/test_utils/runner/android/java/src/com/android/angle/test/AngleNativeTest.java
+
+package org.webrtc.native_test;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Process;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import org.jni_zero.JNINamespace;
+import org.jni_zero.JniType;
+import org.jni_zero.NativeMethods;
+
+import org.webrtc.native_test.StrictModeContext;
+import org.chromium.build.gtest_apk.NativeTestIntent;
+import org.chromium.test.reporter.TestStatusReporter;
+
+import java.io.File;
+
+/** Helper to run tests inside Activity or NativeActivity. */
+@JNINamespace("webrtc::test::android")
+public class NativeTestWebrtc {
+ private static final String TAG = "NativeTestWebrtc";
+
+ private String mCommandLineFilePath;
+ private StringBuilder mCommandLineFlags = new StringBuilder();
+ private TestStatusReporter mReporter;
+ private boolean mRunInSubThread;
+ private String mStdoutFilePath;
+
+ private static class ReportingUncaughtExceptionHandler
+ implements Thread.UncaughtExceptionHandler {
+
+ private TestStatusReporter mReporter;
+ private Thread.UncaughtExceptionHandler mWrappedHandler;
+
+ public ReportingUncaughtExceptionHandler(
+ TestStatusReporter reporter, Thread.UncaughtExceptionHandler wrappedHandler) {
+ mReporter = reporter;
+ mWrappedHandler = wrappedHandler;
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ mReporter.uncaughtException(Process.myPid(), ex);
+ if (mWrappedHandler != null) mWrappedHandler.uncaughtException(thread, ex);
+ }
+ }
+
+ public void preCreate(Activity activity) {
+ String coverageDeviceFile =
+ activity.getIntent().getStringExtra(NativeTestIntent.EXTRA_COVERAGE_DEVICE_FILE);
+ if (coverageDeviceFile != null) {
+ try {
+ Os.setenv("LLVM_PROFILE_FILE", coverageDeviceFile, true);
+ } catch (ErrnoException e) {
+ Log.w(TAG, "failed to set LLVM_PROFILE_FILE" + e.toString());
+ }
+ }
+ // Set TMPDIR to make perfetto_unittests not to use /data/local/tmp as a tmp directory.
+ try {
+ Os.setenv("TMPDIR", activity.getApplicationContext().getCacheDir().getPath(), false);
+ } catch (ErrnoException e) {
+ Log.w(TAG, "failed to set TMPDIR" + e.toString());
+ }
+ }
+
+ public void postCreate(Activity activity) {
+ parseArgumentsFromIntent(activity, activity.getIntent());
+ mReporter = new TestStatusReporter(activity);
+ mReporter.testRunStarted(Process.myPid());
+ Thread.setDefaultUncaughtExceptionHandler(
+ new ReportingUncaughtExceptionHandler(
+ mReporter, Thread.getDefaultUncaughtExceptionHandler()));
+ }
+
+ private void parseArgumentsFromIntent(Activity activity, Intent intent) {
+ Log.i(TAG, "Extras:");
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ for (String s : extras.keySet()) {
+ Log.i(TAG, " " + s);
+ }
+ }
+
+ mCommandLineFilePath = intent.getStringExtra(NativeTestIntent.EXTRA_COMMAND_LINE_FILE);
+ if (mCommandLineFilePath == null) {
+ mCommandLineFilePath = "";
+ } else {
+ File commandLineFile = new File(mCommandLineFilePath);
+ if (!commandLineFile.isAbsolute()) {
+ mCommandLineFilePath =
+ Environment.getExternalStorageDirectory() + "/" + mCommandLineFilePath;
+ }
+ Log.i(TAG, "command line file path: " + mCommandLineFilePath);
+ }
+
+ String commandLineFlags = intent.getStringExtra(NativeTestIntent.EXTRA_COMMAND_LINE_FLAGS);
+ if (commandLineFlags != null) mCommandLineFlags.append(commandLineFlags);
+
+ mRunInSubThread = intent.hasExtra(NativeTestIntent.EXTRA_RUN_IN_SUB_THREAD);
+
+ String gtestFilter = intent.getStringExtra(NativeTestIntent.EXTRA_GTEST_FILTER);
+ if (gtestFilter != null) {
+ appendCommandLineFlags("--gtest_filter=" + gtestFilter);
+ }
+
+ mStdoutFilePath = intent.getStringExtra(NativeTestIntent.EXTRA_STDOUT_FILE);
+ }
+
+ public void appendCommandLineFlags(String flags) {
+ mCommandLineFlags.append(" ").append(flags);
+ }
+
+ public void postStart(final Activity activity, boolean forceRunInSubThread) {
+ final Runnable runTestsTask =
+ new Runnable() {
+ @Override
+ public void run() {
+ runTests(activity);
+ }
+ };
+
+ if (mRunInSubThread || forceRunInSubThread) {
+ // Post a task that posts a task that creates a new thread and runs tests on it.
+
+ // On L and M, the system posts a task to the main thread that prints to stdout
+ // from android::Layout (https://goo.gl/vZA38p). Chaining the subthread creation
+ // through multiple tasks executed on the main thread ensures that this task
+ // runs before we start running tests s.t. its output doesn't interfere with
+ // the test output. See crbug.com/678146 for additional context.
+
+ final Handler handler = new Handler();
+ final Runnable startTestThreadTask =
+ new Runnable() {
+ @Override
+ public void run() {
+ new Thread(runTestsTask).start();
+ }
+ };
+ final Runnable postTestStarterTask =
+ new Runnable() {
+ @Override
+ public void run() {
+ handler.post(startTestThreadTask);
+ }
+ };
+ handler.post(postTestStarterTask);
+ } else {
+ // Post a task to run the tests. This allows us to not block
+ // onCreate and still run tests on the main thread.
+ new Handler().post(runTestsTask);
+ }
+ }
+
+ private void runTests(Activity activity) {
+ Natives jni = NativeTestWebrtcJni.get();
+ String isolated_test_root = NativeTestWebrtc.getIsolatedTestRoot();
+ Log.i(TAG, "Calling into native code");
+ jni.runTests(
+ mCommandLineFlags.toString(),
+ mCommandLineFilePath,
+ mStdoutFilePath,
+ isolated_test_root);
+ Log.i(TAG, "Call into native code returned");
+ activity.finish();
+ mReporter.testRunFinished(Process.myPid());
+ }
+
+
+ // @CalledByNative
+ public static String getIsolatedTestRoot() {
+ try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+ return Environment.getExternalStorageDirectory().getAbsolutePath()
+ + "/chromium_tests_root";
+ }
+ }
+
+ // Signal a failure of the native test loader to python scripts
+ // which run tests. For example, we look for
+ // RUNNER_FAILED build/android/test_package.py.
+ private void nativeTestFailed() {
+ Log.e(TAG, "[ RUNNER_FAILED ] could not load native library");
+ }
+
+ @NativeMethods
+ interface Natives {
+ void runTests(
+ @JniType("std::string") String commandLineFlags,
+ @JniType("std::string") String commandLineFilePath,
+ @JniType("std::string") String stdoutFilePath,
+ @JniType("std::string") String testDataDir);
+ }
+}
\ No newline at end of file
diff --git a/test/android/org/webrtc/native_test/RTCNativeTestApplication.java b/test/android/org/webrtc/native_test/RTCNativeTestApplication.java
new file mode 100644
index 0000000..ce2cf4f
--- /dev/null
+++ b/test/android/org/webrtc/native_test/RTCNativeTestApplication.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 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.native_test;
+
+import android.app.Application;
+import android.content.Context;
+
+/** Application class to be used by native_test apks. */
+public class RTCNativeTestApplication extends Application {
+ @Override
+ protected void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ assert getBaseContext() != null;
+
+ // This is required for Mockito to initialize mocks without running under Instrumentation.
+ System.setProperty("org.mockito.android.target", getCacheDir().getPath());
+ }
+}
\ No newline at end of file
diff --git a/test/android/org/webrtc/native_test/RTCNativeUnitTest.java b/test/android/org/webrtc/native_test/RTCNativeUnitTest.java
index dede7ed..329e1e8 100644
--- a/test/android/org/webrtc/native_test/RTCNativeUnitTest.java
+++ b/test/android/org/webrtc/native_test/RTCNativeUnitTest.java
@@ -11,16 +11,39 @@
package org.webrtc.native_test;
import android.app.Activity;
-import org.chromium.native_test.NativeUnitTest;
+import android.util.Log;
+import org.chromium.build.NativeLibraries;
+import org.webrtc.native_test.NativeTestWebrtc;
import org.webrtc.ContextUtils;
/**
* Native unit test that calls ContextUtils.initialize for WebRTC.
*/
-public class RTCNativeUnitTest extends NativeUnitTest {
- @Override
- public void preCreate(Activity activity) {
- super.preCreate(activity);
- ContextUtils.initialize(activity.getApplicationContext());
- }
-}
+public class RTCNativeUnitTest extends NativeTestWebrtc {
+
+ private static final String TAG = "RTCNativeUnitTest";
+
+ private static final String LIBRARY_UNDER_TEST_NAME =
+ "org.chromium.native_test.NativeTestInstrumentationTestRunner.LibraryUnderTest";
+
+ @Override
+ public void preCreate(Activity activity) {
+ super.preCreate(activity);
+
+ // For NativeActivity based tests, dependency libraries must be loaded before
+ // NativeActivity::OnCreate, otherwise loading android.app.lib_name will fail
+ String libraryToLoad = activity.getIntent().getStringExtra(LIBRARY_UNDER_TEST_NAME);
+ loadLibraries(
+ libraryToLoad != null ? new String[] {libraryToLoad} : NativeLibraries.LIBRARIES);
+
+ ContextUtils.initialize(activity.getApplicationContext());
+ }
+
+ private void loadLibraries(String[] librariesToLoad) {
+ for (String library : librariesToLoad) {
+ Log.i(TAG, "loading: " + library);
+ System.loadLibrary(library);
+ Log.i(TAG, "loaded: " + library);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/android/org/webrtc/native_test/StrictModeContext.java b/test/android/org/webrtc/native_test/StrictModeContext.java
new file mode 100644
index 0000000..24e036c
--- /dev/null
+++ b/test/android/org/webrtc/native_test/StrictModeContext.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Based on Chromium's https://source.chromium.org/chromium/chromium/src/+/main:base/android/java/src/org/chromium/base/StrictModeContext.java
+
+package org.webrtc.native_test;
+
+import android.os.Build;
+import android.os.StrictMode;
+
+import java.io.Closeable;
+
+/**
+ * Enables try-with-resources compatible StrictMode violation allowlisting.
+ *
+ * <p>Prefer "ignored" as the variable name to appease Android Studio's "Unused symbol" inspection.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
+ * return Example.doThingThatRequiresDiskWrites();
+ * }
+ * </pre>
+ */
+public class StrictModeContext implements Closeable {
+ private static class Impl extends StrictModeContext {
+ private final StrictMode.ThreadPolicy mThreadPolicy;
+ private final StrictMode.VmPolicy mVmPolicy;
+
+ private Impl(StrictMode.ThreadPolicy threadPolicy, StrictMode.VmPolicy vmPolicy) {
+ mThreadPolicy = threadPolicy;
+ mVmPolicy = vmPolicy;
+ }
+
+ private Impl(StrictMode.ThreadPolicy threadPolicy) {
+ this(threadPolicy, null);
+ }
+
+ private Impl(StrictMode.VmPolicy vmPolicy) {
+ this(null, vmPolicy);
+ }
+
+ @Override
+ public void close() {
+ if (mThreadPolicy != null) {
+ StrictMode.setThreadPolicy(mThreadPolicy);
+ }
+ if (mVmPolicy != null) {
+ StrictMode.setVmPolicy(mVmPolicy);
+ }
+ }
+ }
+
+ /**
+ * Convenience method for disabling all VM-level StrictMode checks with try-with-resources.
+ * Includes everything listed here:
+ * https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html
+ */
+ public static StrictModeContext allowAllVmPolicies() {
+ StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
+ StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX);
+ return new Impl(oldPolicy);
+ }
+
+ /**
+ * Convenience method for disabling all thread-level StrictMode checks with try-with-resources.
+ * Includes everything listed here:
+ * https://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder.html
+ */
+ public static StrictModeContext allowAllThreadPolicies() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX);
+ return new Impl(oldPolicy);
+ }
+
+ /** Convenience method for disabling StrictMode for disk-writes with try-with-resources. */
+ public static StrictModeContext allowDiskWrites() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ return new Impl(oldPolicy);
+ }
+
+ /** Convenience method for disabling StrictMode for disk-reads with try-with-resources. */
+ public static StrictModeContext allowDiskReads() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ return new Impl(oldPolicy);
+ }
+
+ /** Convenience method for disabling StrictMode for slow calls with try-with-resources. */
+ public static StrictModeContext allowSlowCalls() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder(oldPolicy).permitCustomSlowCalls().build());
+ return new Impl(oldPolicy);
+ }
+
+ /**
+ * Convenience method for disabling StrictMode for unbuffered input/output operations with
+ * try-with-resources. For API level 25- this method will do nothing; because
+ * StrictMode.ThreadPolicy.Builder#permitUnbufferedIo is added in API level 26.
+ */
+ public static StrictModeContext allowUnbufferedIo() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder(oldPolicy)
+ .permitUnbufferedIo()
+ .build());
+ }
+ return new Impl(oldPolicy);
+ }
+
+ @Override
+ public void close() {}
+}
\ No newline at end of file
diff --git a/webrtc.gni b/webrtc.gni
index 6a09451..ba5c39d 100644
--- a/webrtc.gni
+++ b/webrtc.gni
@@ -525,13 +525,17 @@
public_configs += invoker.public_configs
}
if (!build_with_chromium && is_android) {
+ use_default_launcher = false
android_manifest = webrtc_root + "test/android/AndroidManifest.xml"
use_raw_android_executable = false
min_sdk_version = 21
target_sdk_version = 23
deps += [
"//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
+ webrtc_root + "sdk/android:native_test_jni_onload",
+ webrtc_root + "sdk/android:base_java",
webrtc_root + "test:native_test_java",
+ webrtc_root + "test:native_test_support",
]
}