Snap for 9850934 from 5ab99d1ec1f8661ff812fe5bdb855529f81a4c00 to mainline-resolv-release

Change-Id: Ic1d8531bf67e921f366df27ea8485b99df302fe0
diff --git a/Android.bp b/Android.bp
index b238396..e20a86d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1829,6 +1829,7 @@
         ":perfetto_src_tracing_core_core",
         ":perfetto_src_tracing_core_service",
         ":perfetto_src_tracing_in_process_backend",
+        ":perfetto_src_tracing_integrationtests",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
         ":perfetto_src_tracing_ipc_default_socket",
@@ -9313,6 +9314,14 @@
     ],
 }
 
+// GN: //src/tracing:integrationtests
+filegroup {
+    name: "perfetto_src_tracing_integrationtests",
+    srcs: [
+        "src/tracing/internal/tracing_muxer_impl_integrationtest.cc",
+    ],
+}
+
 // GN: //src/tracing/ipc:common
 filegroup {
     name: "perfetto_src_tracing_ipc_common",
diff --git a/BUILD b/BUILD
index 76401ad..fc8ef7d 100644
--- a/BUILD
+++ b/BUILD
@@ -3096,6 +3096,7 @@
     ],
     deps = [
         ":protos_perfetto_common_protos",
+        ":protos_perfetto_trace_android_protos",
         ":protos_perfetto_trace_gpu_protos",
         ":protos_perfetto_trace_profiling_protos",
         ":protos_perfetto_trace_track_event_protos",
@@ -3107,6 +3108,7 @@
     name = "protos_perfetto_trace_interned_data_zero",
     deps = [
         ":protos_perfetto_common_zero",
+        ":protos_perfetto_trace_android_zero",
         ":protos_perfetto_trace_gpu_zero",
         ":protos_perfetto_trace_interned_data_protos",
         ":protos_perfetto_trace_profiling_zero",
diff --git a/CHANGELOG b/CHANGELOG
index 4b35e38..6a4cdbd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,8 @@
   SDK:
     * Added TracingInitArgs::enable_system_consumer configuration option, that
       allows the linker to discard the consumer IPC, if not required.
+    * Add perfetto::Tracing::ActivateTriggers() function.
+
 
 v25.0 - 2022-04-01:
   Tracing service and probes:
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 785bb2a..b30e56b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -9,6 +9,16 @@
       "name": "libsurfaceflinger_unittest"
     }
   ],
+  "mainline-presubmit": [
+    {
+      "name": "CtsPerfettoTestCases[com.google.android.art.apex]",
+      "options": [
+        {
+          "include-filter": "HeapprofdJavaCtsTest*"
+        }
+      ]
+    }
+  ],
   "hwasan-postsubmit": [
     {
       "name": "CtsPerfettoTestCases"
diff --git a/gn/perfetto_integrationtests.gni b/gn/perfetto_integrationtests.gni
index 7527a55..4e47083 100644
--- a/gn/perfetto_integrationtests.gni
+++ b/gn/perfetto_integrationtests.gni
@@ -20,6 +20,11 @@
   "src/tracing/test:client_api_integrationtests",
 ]
 
+if (enable_perfetto_ipc) {
+  perfetto_integrationtests_targets +=
+      [ "src/tracing:integrationtests" ]
+}
+
 if (enable_perfetto_traced_probes) {
   # enable_perfetto_traced_probes implies enable_perfetto_platform_services.
   perfetto_integrationtests_targets += [
diff --git a/include/perfetto/ext/base/utils.h b/include/perfetto/ext/base/utils.h
index 1535c5e..4c4b66b 100644
--- a/include/perfetto/ext/base/utils.h
+++ b/include/perfetto/ext/base/utils.h
@@ -98,6 +98,9 @@
 // setenv(2)-equivalent. Deals with Windows vs Posix discrepancies.
 void SetEnv(const std::string& key, const std::string& value);
 
+// unsetenv(2)-equivalent. Deals with Windows vs Posix discrepancies.
+void UnsetEnv(const std::string& key);
+
 // Calls mallopt(M_PURGE, 0) on Android. Does nothing on other platforms.
 // This forces the allocator to release freed memory. This is used to work
 // around various Scudo inefficiencies. See b/170217718.
diff --git a/include/perfetto/tracing/internal/tracing_muxer.h b/include/perfetto/tracing/internal/tracing_muxer.h
index bef7191..962763c 100644
--- a/include/perfetto/tracing/internal/tracing_muxer.h
+++ b/include/perfetto/tracing/internal/tracing_muxer.h
@@ -88,6 +88,15 @@
                                    InterceptorBase::TLSFactory,
                                    InterceptorBase::TracePacketCallback) = 0;
 
+  // Informs the tracing services to activate any of these triggers if any
+  // tracing session was waiting for them.
+  //
+  // Sends the trigger signal to all the initialized backends that are currently
+  // connected and that connect in the next `ttl_ms` milliseconds (but returns
+  // immediately anyway).
+  virtual void ActivateTriggers(const std::vector<std::string>&,
+                                uint32_t ttl_ms) = 0;
+
  protected:
   explicit TracingMuxer(Platform* platform) : platform_(platform) {}
 
diff --git a/include/perfetto/tracing/tracing.h b/include/perfetto/tracing/tracing.h
index 01d4851..8faa1de 100644
--- a/include/perfetto/tracing/tracing.h
+++ b/include/perfetto/tracing/tracing.h
@@ -194,6 +194,15 @@
   // this call is made.
   static void ResetForTesting();
 
+  // Informs the tracing services to activate any of these triggers if any
+  // tracing session was waiting for them.
+  //
+  // Sends the trigger signal to all the initialized backends that are currently
+  // connected and that connect in the next `ttl_ms` milliseconds (but
+  // returns immediately anyway).
+  static void ActivateTriggers(const std::vector<std::string>& triggers,
+                               uint32_t ttl_ms);
+
  private:
   static void InitializeInternal(const TracingInitArgs&);
 
diff --git a/protos/perfetto/config/android/network_trace_config.proto b/protos/perfetto/config/android/network_trace_config.proto
index f58f280..ad5196b 100644
--- a/protos/perfetto/config/android/network_trace_config.proto
+++ b/protos/perfetto/config/android/network_trace_config.proto
@@ -27,4 +27,28 @@
   // The minimum polling rate is 100ms (values below this are ignored).
   // Introduced in Android 14 (U).
   optional uint32 poll_ms = 1;
+
+  // The aggregation_threshold is the number of packets at which an event will
+  // switch from per-packet details to aggregate details. For example, a value
+  // of 50 means that if a particular event (grouped by the unique combinations
+  // of metadata fields: {interface, direction, uid, etc}) has fewer than 50
+  // packets, the exact timestamp and length are recorded for each packet. If
+  // there were 50 or more packets in an event, it would only record the total
+  // duration, packets, and length. A value of zero or unspecified will always
+  /// record per-packet details. A value of 1 always records aggregate details.
+  optional uint32 aggregation_threshold = 2;
+
+  // Specifies the maximum number of packet contexts to intern at a time. This
+  // prevents the interning table from growing too large and controls whether
+  // interning is enabled or disabled (a value of zero disables interning and
+  // is the default). When a data sources interning table reaches this amount,
+  // packet contexts will be inlined into NetworkPacketEvents.
+  optional uint32 intern_limit = 3;
+
+  // The following fields specify whether certain fields should be dropped from
+  // the output. Dropping fields improves normalization results, reduces the
+  // size of the interning table, and slightly reduces event size.
+  optional bool drop_local_port = 4;
+  optional bool drop_remote_port = 5;
+  optional bool drop_tcp_flags = 6;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index b809855..3b920d5 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -402,6 +402,30 @@
   // The minimum polling rate is 100ms (values below this are ignored).
   // Introduced in Android 14 (U).
   optional uint32 poll_ms = 1;
+
+  // The aggregation_threshold is the number of packets at which an event will
+  // switch from per-packet details to aggregate details. For example, a value
+  // of 50 means that if a particular event (grouped by the unique combinations
+  // of metadata fields: {interface, direction, uid, etc}) has fewer than 50
+  // packets, the exact timestamp and length are recorded for each packet. If
+  // there were 50 or more packets in an event, it would only record the total
+  // duration, packets, and length. A value of zero or unspecified will always
+  /// record per-packet details. A value of 1 always records aggregate details.
+  optional uint32 aggregation_threshold = 2;
+
+  // Specifies the maximum number of packet contexts to intern at a time. This
+  // prevents the interning table from growing too large and controls whether
+  // interning is enabled or disabled (a value of zero disables interning and
+  // is the default). When a data sources interning table reaches this amount,
+  // packet contexts will be inlined into NetworkPacketEvents.
+  optional uint32 intern_limit = 3;
+
+  // The following fields specify whether certain fields should be dropped from
+  // the output. Dropping fields improves normalization results, reduces the
+  // size of the interning table, and slightly reduces event size.
+  optional bool drop_local_port = 4;
+  optional bool drop_remote_port = 5;
+  optional bool drop_tcp_flags = 6;
 }
 
 // End of protos/perfetto/config/android/network_trace_config.proto
diff --git a/protos/perfetto/trace/android/network_trace.proto b/protos/perfetto/trace/android/network_trace.proto
index d1c43a2..5fcd28b 100644
--- a/protos/perfetto/trace/android/network_trace.proto
+++ b/protos/perfetto/trace/android/network_trace.proto
@@ -33,7 +33,9 @@
   // The name of the interface if available (e.g. 'rmnet0').
   optional string interface = 2;
 
-  // The length of the packet in bytes (wire_size - L2_header_size).
+  // The length of the packet in bytes (wire_size - L2_header_size). Ignored
+  // when using NetworkPacketEvent as the ctx in either NetworkPacketBundle or
+  // NetworkPacketContext.
   optional uint32 length = 3;
 
   // The Linux user id associated with the packet's socket.
@@ -54,3 +56,36 @@
   // The remote udp/tcp port of the packet.
   optional uint32 remote_port = 9;
 }
+
+// NetworkPacketBundle bundles one or more packets sharing the same attributes.
+message NetworkPacketBundle {
+  oneof packet_context {
+    // The intern id for looking up the associated packet context.
+    uint64 iid = 1;
+
+    // The inlined context for events in this bundle.
+    NetworkPacketEvent ctx = 2;
+  }
+
+  // The timestamp of the i-th packet encoded as the nanoseconds since the
+  // enclosing TracePacket's timestamp.
+  repeated uint64 packet_timestamps = 3 [packed = true];
+
+  // The length of the i-th packet in bytes (wire_size - L2_header_size).
+  repeated uint32 packet_lengths = 4 [packed = true];
+
+  // Total number of packets in the bundle (when above aggregation_threshold).
+  optional uint32 total_packets = 5;
+
+  // Duration between first and last packet (when above aggregation_threshold).
+  optional uint64 total_duration = 6;
+
+  // Total packet length in bytes (when above aggregation_threshold).
+  optional uint64 total_length = 7;
+}
+
+// An internable packet context.
+message NetworkPacketContext {
+  optional uint64 iid = 1;
+  optional NetworkPacketEvent ctx = 2;
+}
diff --git a/protos/perfetto/trace/interned_data/BUILD.gn b/protos/perfetto/trace/interned_data/BUILD.gn
index 580bb5b..66436c5 100644
--- a/protos/perfetto/trace/interned_data/BUILD.gn
+++ b/protos/perfetto/trace/interned_data/BUILD.gn
@@ -17,6 +17,7 @@
 perfetto_proto_library("@TYPE@") {
   sources = [ "interned_data.proto" ]
   deps = [
+    "../android:@TYPE@",
     "../gpu:@TYPE@",
     "../profiling:@TYPE@",
     "../track_event:@TYPE@",
diff --git a/protos/perfetto/trace/interned_data/interned_data.proto b/protos/perfetto/trace/interned_data/interned_data.proto
index 86b8a9f..19894ca 100644
--- a/protos/perfetto/trace/interned_data/interned_data.proto
+++ b/protos/perfetto/trace/interned_data/interned_data.proto
@@ -16,6 +16,7 @@
 
 syntax = "proto2";
 
+import "protos/perfetto/trace/android/network_trace.proto";
 import "protos/perfetto/trace/gpu/gpu_render_stage_event.proto";
 import "protos/perfetto/trace/track_event/chrome_histogram_sample.proto";
 import "protos/perfetto/trace/track_event/debug_annotation.proto";
@@ -53,7 +54,7 @@
 // emitted proactively in advance of referring to them in later packets.
 //
 // Next reserved id: 8 (up to 15).
-// Next id: 28.
+// Next id: 31.
 message InternedData {
   // TODO(eseckler): Replace iid fields inside interned messages with
   // map<iid, message> type fields in InternedData.
@@ -110,4 +111,7 @@
   // This is is NOT the real address. This is to avoid disclosing KASLR through
   // traces.
   repeated InternedString kernel_symbols = 26;
+
+  // Interned packet context for android.network_packets.
+  repeated NetworkPacketContext packet_context = 30;
 }
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 8056c83..e4e75a2 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -402,6 +402,30 @@
   // The minimum polling rate is 100ms (values below this are ignored).
   // Introduced in Android 14 (U).
   optional uint32 poll_ms = 1;
+
+  // The aggregation_threshold is the number of packets at which an event will
+  // switch from per-packet details to aggregate details. For example, a value
+  // of 50 means that if a particular event (grouped by the unique combinations
+  // of metadata fields: {interface, direction, uid, etc}) has fewer than 50
+  // packets, the exact timestamp and length are recorded for each packet. If
+  // there were 50 or more packets in an event, it would only record the total
+  // duration, packets, and length. A value of zero or unspecified will always
+  /// record per-packet details. A value of 1 always records aggregate details.
+  optional uint32 aggregation_threshold = 2;
+
+  // Specifies the maximum number of packet contexts to intern at a time. This
+  // prevents the interning table from growing too large and controls whether
+  // interning is enabled or disabled (a value of zero disables interning and
+  // is the default). When a data sources interning table reaches this amount,
+  // packet contexts will be inlined into NetworkPacketEvents.
+  optional uint32 intern_limit = 3;
+
+  // The following fields specify whether certain fields should be dropped from
+  // the output. Dropping fields improves normalization results, reduces the
+  // size of the interning table, and slightly reduces event size.
+  optional bool drop_local_port = 4;
+  optional bool drop_remote_port = 5;
+  optional bool drop_tcp_flags = 6;
 }
 
 // End of protos/perfetto/config/android/network_trace_config.proto
@@ -2838,7 +2862,9 @@
   // The name of the interface if available (e.g. 'rmnet0').
   optional string interface = 2;
 
-  // The length of the packet in bytes (wire_size - L2_header_size).
+  // The length of the packet in bytes (wire_size - L2_header_size). Ignored
+  // when using NetworkPacketEvent as the ctx in either NetworkPacketBundle or
+  // NetworkPacketContext.
   optional uint32 length = 3;
 
   // The Linux user id associated with the packet's socket.
@@ -2860,6 +2886,39 @@
   optional uint32 remote_port = 9;
 }
 
+// NetworkPacketBundle bundles one or more packets sharing the same attributes.
+message NetworkPacketBundle {
+  oneof packet_context {
+    // The intern id for looking up the associated packet context.
+    uint64 iid = 1;
+
+    // The inlined context for events in this bundle.
+    NetworkPacketEvent ctx = 2;
+  }
+
+  // The timestamp of the i-th packet encoded as the nanoseconds since the
+  // enclosing TracePacket's timestamp.
+  repeated uint64 packet_timestamps = 3 [packed = true];
+
+  // The length of the i-th packet in bytes (wire_size - L2_header_size).
+  repeated uint32 packet_lengths = 4 [packed = true];
+
+  // Total number of packets in the bundle (when above aggregation_threshold).
+  optional uint32 total_packets = 5;
+
+  // Duration between first and last packet (when above aggregation_threshold).
+  optional uint64 total_duration = 6;
+
+  // Total packet length in bytes (when above aggregation_threshold).
+  optional uint64 total_length = 7;
+}
+
+// An internable packet context.
+message NetworkPacketContext {
+  optional uint64 iid = 1;
+  optional NetworkPacketEvent ctx = 2;
+}
+
 // End of protos/perfetto/trace/android/network_trace.proto
 
 // Begin of protos/perfetto/trace/android/packages_list.proto
@@ -8376,7 +8435,7 @@
 // emitted proactively in advance of referring to them in later packets.
 //
 // Next reserved id: 8 (up to 15).
-// Next id: 28.
+// Next id: 31.
 message InternedData {
   // TODO(eseckler): Replace iid fields inside interned messages with
   // map<iid, message> type fields in InternedData.
@@ -8433,6 +8492,9 @@
   // This is is NOT the real address. This is to avoid disclosing KASLR through
   // traces.
   repeated InternedString kernel_symbols = 26;
+
+  // Interned packet context for android.network_packets.
+  repeated NetworkPacketContext packet_context = 30;
 }
 
 // End of protos/perfetto/trace/interned_data/interned_data.proto
@@ -10134,7 +10196,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 89.
+// Next id: 93.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -10238,6 +10300,9 @@
     // Represents a single packet sent or received by the network.
     NetworkPacketEvent network_packet = 88;
 
+    // Represents one or more packets sent or received by the network.
+    NetworkPacketBundle network_packet_bundle = 92;
+
     // This field is only used for testing.
     // In previous versions of this proto this field had the id 268435455
     // This caused many problems:
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 2eb602a..822d360 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -88,7 +88,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 14 (up to 15).
-// Next id: 89.
+// Next id: 93.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -192,6 +192,9 @@
     // Represents a single packet sent or received by the network.
     NetworkPacketEvent network_packet = 88;
 
+    // Represents one or more packets sent or received by the network.
+    NetworkPacketBundle network_packet_bundle = 92;
+
     // This field is only used for testing.
     // In previous versions of this proto this field had the id 268435455
     // This caused many problems:
diff --git a/src/base/test/tmp_dir_tree.cc b/src/base/test/tmp_dir_tree.cc
index 9ed785f..8b70ec0 100644
--- a/src/base/test/tmp_dir_tree.cc
+++ b/src/base/test/tmp_dir_tree.cc
@@ -44,12 +44,16 @@
 
 void TmpDirTree::AddFile(const std::string& relative_path,
                          const std::string& content) {
-  files_to_remove_.push(relative_path);
+  TrackFile(relative_path);
   base::ScopedFile fd(base::OpenFile(AbsolutePath(relative_path),
                                      O_WRONLY | O_CREAT | O_TRUNC, 0600));
   PERFETTO_CHECK(base::WriteAll(fd.get(), content.c_str(), content.size()) ==
                  static_cast<ssize_t>(content.size()));
 }
 
+void TmpDirTree::TrackFile(const std::string& relative_path) {
+  files_to_remove_.push(relative_path);
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/test/tmp_dir_tree.h b/src/base/test/tmp_dir_tree.h
index 6bb730c..9b282f8 100644
--- a/src/base/test/tmp_dir_tree.h
+++ b/src/base/test/tmp_dir_tree.h
@@ -47,6 +47,11 @@
   // succeeds.
   void AddFile(const std::string& relative_path, const std::string& content);
 
+  // Tells this object to remove `relative_path` on destruction. This is used
+  // for files created by the caller that still need to be removed on cleanup by
+  // this object.
+  void TrackFile(const std::string& relative_path);
+
  private:
   TmpDirTree(const TmpDirTree&) = delete;
   TmpDirTree& operator=(const TmpDirTree&) = delete;
diff --git a/src/base/utils.cc b/src/base/utils.cc
index b4da80c..b72c269 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -172,6 +172,14 @@
 #endif
 }
 
+void UnsetEnv(const std::string& key) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  PERFETTO_CHECK(::_putenv_s(key.c_str(), "") == 0);
+#else
+  PERFETTO_CHECK(::unsetenv(key.c_str()) == 0);
+#endif
+}
+
 void Daemonize(std::function<int()> parent_cb) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index ebf198c..25eb0c7 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -135,6 +135,21 @@
   }
 }
 
+# Separate target because the embedder might not want this.
+source_set("integrationtests") {
+  testonly = true
+  deps = [
+    "../../gn:default_deps",
+    "../../gn:gtest_and_gmock",
+    "../../include/perfetto/ext/tracing/ipc",
+    "../../include/perfetto/tracing",
+    "../../protos/perfetto/trace:cpp",
+    "../base",
+    "../base:test_support",
+  ]
+  sources = [ "internal/tracing_muxer_impl_integrationtest.cc" ]
+}
+
 perfetto_unittest_source_set("unittests") {
   testonly = true
   deps = [
diff --git a/src/tracing/internal/tracing_muxer_fake.cc b/src/tracing/internal/tracing_muxer_fake.cc
index a6700f3..07d8f67 100644
--- a/src/tracing/internal/tracing_muxer_fake.cc
+++ b/src/tracing/internal/tracing_muxer_fake.cc
@@ -85,5 +85,10 @@
   FailUninitialized();
 }
 
+void TracingMuxerFake::ActivateTriggers(const std::vector<std::string>&,
+                                        uint32_t) {
+  FailUninitialized();
+}
+
 }  // namespace internal
 }  // namespace perfetto
diff --git a/src/tracing/internal/tracing_muxer_fake.h b/src/tracing/internal/tracing_muxer_fake.h
index 78fc769..e2bcd8e 100644
--- a/src/tracing/internal/tracing_muxer_fake.h
+++ b/src/tracing/internal/tracing_muxer_fake.h
@@ -67,6 +67,7 @@
                            InterceptorFactory,
                            InterceptorBase::TLSFactory,
                            InterceptorBase::TracePacketCallback) override;
+  void ActivateTriggers(const std::vector<std::string>&, uint32_t) override;
 
  private:
   static TracingMuxerFake instance;
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 876b90b..b1f6507 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -180,6 +180,7 @@
   PERFETTO_DCHECK(!connected_);
   connected_ = true;
   muxer_->UpdateDataSourcesOnAllBackends();
+  SendOnConnectTriggers();
 }
 
 void TracingMuxerImpl::ProducerImpl::OnDisconnect() {
@@ -280,6 +281,22 @@
   return dead_services_.empty();
 }
 
+void TracingMuxerImpl::ProducerImpl::SendOnConnectTriggers() {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  base::TimeMillis now = base::GetWallTimeMs();
+  std::vector<std::string> triggers;
+  while (!on_connect_triggers_.empty()) {
+    // Skip if we passed TTL.
+    if (on_connect_triggers_.front().second > now) {
+      triggers.push_back(std::move(on_connect_triggers_.front().first));
+    }
+    on_connect_triggers_.pop_front();
+  }
+  if (!triggers.empty()) {
+    service_->ActivateTriggers(triggers);
+  }
+}
+
 // ----- End of TracingMuxerImpl::ProducerImpl methods.
 
 // ----- Begin of TracingMuxerImpl::ConsumerImpl
@@ -894,6 +911,25 @@
       });
 }
 
+void TracingMuxerImpl::ActivateTriggers(
+    const std::vector<std::string>& triggers,
+    uint32_t ttl_ms) {
+  base::TimeMillis expire_time =
+      base::GetWallTimeMs() + base::TimeMillis(ttl_ms);
+  task_runner_->PostTask([this, triggers, expire_time] {
+    for (RegisteredBackend& backend : backends_) {
+      if (backend.producer->connected_) {
+        backend.producer->service_->ActivateTriggers(triggers);
+      } else {
+        for (const std::string& trigger : triggers) {
+          backend.producer->on_connect_triggers_.emplace_back(trigger,
+                                                              expire_time);
+        }
+      }
+    }
+  });
+}
+
 // Called by the service of one of the backends.
 void TracingMuxerImpl::SetupDataSource(TracingBackendId backend_id,
                                        uint32_t backend_connection_id,
diff --git a/src/tracing/internal/tracing_muxer_impl.h b/src/tracing/internal/tracing_muxer_impl.h
index c545a0b..08a2111 100644
--- a/src/tracing/internal/tracing_muxer_impl.h
+++ b/src/tracing/internal/tracing_muxer_impl.h
@@ -28,6 +28,7 @@
 #include <memory>
 #include <vector>
 
+#include "perfetto/base/time.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/thread_checker.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
@@ -116,6 +117,8 @@
                            InterceptorBase::TLSFactory,
                            InterceptorBase::TracePacketCallback) override;
 
+  void ActivateTriggers(const std::vector<std::string>&, uint32_t) override;
+
   std::unique_ptr<TracingSession> CreateTracingSession(BackendType);
 
   // Producer-side bookkeeping methods.
@@ -196,6 +199,7 @@
     void ClearIncrementalState(const DataSourceInstanceID*, size_t) override;
 
     bool SweepDeadServices();
+    void SendOnConnectTriggers();
 
     PERFETTO_THREAD_CHECKER(thread_checker_)
     TracingMuxerImpl* muxer_;
@@ -217,6 +221,10 @@
     // that no longer have any writers (see SweepDeadServices).
     std::list<std::shared_ptr<ProducerEndpoint>> dead_services_;
 
+    // Triggers that should be sent when the service connects (trigger_name,
+    // expiration).
+    std::list<std::pair<std::string, base::TimeMillis>> on_connect_triggers_;
+
     // The currently active service endpoint is maintained as an atomic shared
     // pointer so it won't get deleted from underneath threads that are creating
     // trace writers. At any given time one endpoint can be shared (and thus
diff --git a/src/tracing/internal/tracing_muxer_impl_integrationtest.cc b/src/tracing/internal/tracing_muxer_impl_integrationtest.cc
new file mode 100644
index 0000000..e1cfbfd
--- /dev/null
+++ b/src/tracing/internal/tracing_muxer_impl_integrationtest.cc
@@ -0,0 +1,172 @@
+#include "perfetto/tracing/tracing.h"
+
+#include <stdio.h>
+
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/thread_task_runner.h"
+#include "perfetto/ext/base/waitable_event.h"
+#include "perfetto/ext/tracing/ipc/service_ipc_host.h"
+#include "perfetto/tracing/backend_type.h"
+#include "protos/perfetto/config/trace_config.gen.h"
+#include "protos/perfetto/trace/trace.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "protos/perfetto/trace/trigger.gen.h"
+#include "src/base/test/test_task_runner.h"
+#include "src/base/test/tmp_dir_tree.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace internal {
+namespace {
+
+using ::testing::NiceMock;
+using ::testing::NotNull;
+using ::testing::Property;
+
+class TracingMuxerImplIntegrationTest : public testing::Test {
+ protected:
+  // Sets the environment variable `name` to `value`. Restores it to the
+  // previous value when the test finishes.
+  void SetEnvVar(const char* name, const char* value) {
+    prev_state_.emplace();
+    EnvVar& var = prev_state_.top();
+    var.name = name;
+    const char* prev_value = getenv(name);
+    if (prev_value) {
+      var.value.emplace(prev_value);
+    }
+    base::SetEnv(name, value);
+  }
+
+  ~TracingMuxerImplIntegrationTest() {
+    perfetto::Tracing::ResetForTesting();
+    while (!prev_state_.empty()) {
+      const EnvVar& var = prev_state_.top();
+      if (var.value) {
+        base::SetEnv(var.name, *var.value);
+      } else {
+        base::UnsetEnv(var.name);
+      }
+      prev_state_.pop();
+    }
+  }
+
+  struct EnvVar {
+    const char* name;
+    base::Optional<std::string> value;
+  };
+  // Stores previous values of environment variables overridden by tests. We
+  // need to to this because some android integration tests need to talk to the
+  // real system tracing service and need the PERFETTO_PRODUCER_SOCK_NAME and
+  // PERFETTO_CONSUMER_SOCK_NAME to be set to their original value.
+  std::stack<EnvVar> prev_state_;
+};
+
+class TracingServiceThread {
+ public:
+  TracingServiceThread(const std::string& producer_socket,
+                       const std::string& consumer_socket)
+      : runner_(base::ThreadTaskRunner::CreateAndStart("perfetto.svc")),
+        producer_socket_(producer_socket),
+        consumer_socket_(consumer_socket) {
+    runner_.PostTaskAndWaitForTesting([this]() {
+      svc_ = ServiceIPCHost::CreateInstance(&runner_);
+      bool res =
+          svc_->Start(producer_socket_.c_str(), consumer_socket_.c_str());
+      if (!res) {
+        PERFETTO_FATAL("Failed to start service listening on %s and %s",
+                       producer_socket_.c_str(), consumer_socket_.c_str());
+      }
+    });
+  }
+
+  ~TracingServiceThread() {
+    runner_.PostTaskAndWaitForTesting([this]() { svc_.reset(); });
+  }
+
+  const std::string& producer_socket() const { return producer_socket_; }
+  const std::string& consumer_socket() const { return consumer_socket_; }
+
+ private:
+  base::ThreadTaskRunner runner_;
+
+  std::string producer_socket_;
+  std::string consumer_socket_;
+  std::unique_ptr<ServiceIPCHost> svc_;
+};
+
+TEST_F(TracingMuxerImplIntegrationTest, ActivateTriggers) {
+  base::TmpDirTree tmpdir_;
+
+  base::TestTaskRunner task_runner;
+
+  ASSERT_FALSE(perfetto::Tracing::IsInitialized());
+
+  tmpdir_.TrackFile("producer2.sock");
+  tmpdir_.TrackFile("consumer.sock");
+  TracingServiceThread tracing_service(tmpdir_.AbsolutePath("producer2.sock"),
+                                       tmpdir_.AbsolutePath("consumer.sock"));
+  // Instead of being a unix socket, producer.sock is a regular empty file.
+  tmpdir_.AddFile("producer.sock", "");
+
+  // Wrong producer socket: the producer won't connect yet, but the consumer
+  // will.
+  SetEnvVar("PERFETTO_PRODUCER_SOCK_NAME",
+            tmpdir_.AbsolutePath("producer.sock").c_str());
+  SetEnvVar("PERFETTO_CONSUMER_SOCK_NAME",
+            tmpdir_.AbsolutePath("consumer.sock").c_str());
+
+  TracingInitArgs args;
+  args.backends = perfetto::kSystemBackend;
+  perfetto::Tracing::Initialize(args);
+
+  // TracingMuxerImpl::ActivateTriggers will be called without the producer side
+  // of the service being connected. It should store the trigger for 10000ms.
+  perfetto::Tracing::ActivateTriggers({"trigger2", "trigger1"}, 10000);
+
+  perfetto::TraceConfig cfg;
+  cfg.add_buffers()->set_size_kb(1024);
+  perfetto::TraceConfig::TriggerConfig* tr_cfg = cfg.mutable_trigger_config();
+  tr_cfg->set_trigger_mode(perfetto::TraceConfig::TriggerConfig::STOP_TRACING);
+  tr_cfg->set_trigger_timeout_ms(10000);
+  perfetto::TraceConfig::TriggerConfig::Trigger* trigger =
+      tr_cfg->add_triggers();
+  trigger->set_name("trigger1");
+
+  std::unique_ptr<TracingSession> session =
+      perfetto::Tracing::NewTrace(perfetto::kSystemBackend);
+  base::WaitableEvent on_stop;
+  session->SetOnStopCallback([&on_stop] { on_stop.Notify(); });
+  session->Setup(cfg);
+
+  session->StartBlocking();
+
+  // Swap producer.sock and producer2.sock. Now the client should connect to the
+  // tracing service as a producer.
+  ASSERT_EQ(rename(tmpdir_.AbsolutePath("producer2.sock").c_str(),
+                   tmpdir_.AbsolutePath("producer3.sock").c_str()),
+            0);
+  ASSERT_EQ(rename(tmpdir_.AbsolutePath("producer.sock").c_str(),
+                   tmpdir_.AbsolutePath("producer2.sock").c_str()),
+            0);
+  ASSERT_EQ(rename(tmpdir_.AbsolutePath("producer3.sock").c_str(),
+                   tmpdir_.AbsolutePath("producer.sock").c_str()),
+            0);
+
+  on_stop.Wait();
+
+  std::vector<char> bytes = session->ReadTraceBlocking();
+  perfetto::protos::gen::Trace parsed_trace;
+  ASSERT_TRUE(parsed_trace.ParseFromArray(bytes.data(), bytes.size()));
+  EXPECT_THAT(
+      parsed_trace,
+      Property(&perfetto::protos::gen::Trace::packet,
+               Contains(Property(
+                   &perfetto::protos::gen::TracePacket::trigger,
+                   Property(&perfetto::protos::gen::Trigger::trigger_name,
+                            "trigger1")))));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace perfetto
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 98963fc..6b945f9 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -91,6 +91,7 @@
 #include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_descriptor.gen.h"
 #include "protos/perfetto/trace/track_event/track_event.gen.h"
+#include "protos/perfetto/trace/trigger.gen.h"
 
 // Events in categories starting with "dynamic" will use dynamic category
 // lookup.
@@ -187,6 +188,7 @@
 using perfetto::internal::TrackEventInternal;
 using ::testing::_;
 using ::testing::ContainerEq;
+using ::testing::Contains;
 using ::testing::ElementsAre;
 using ::testing::HasSubstr;
 using ::testing::Invoke;
@@ -313,6 +315,8 @@
       perfetto::InterceptorBase::TLSFactory,
       perfetto::InterceptorBase::TracePacketCallback) override {}
 
+  void ActivateTriggers(const std::vector<std::string>&, uint32_t) override {}
+
   std::vector<DataSource> data_sources;
 
  private:
@@ -452,11 +456,13 @@
     g_test_tracing_policy->should_allow_consumer_connection = true;
 
     // Start a fresh system service for this test, tearing down any previous
-    // service that was running. If the system backend isn't supported, skip all
-    // system backend tests.
-    if (GetParam() == perfetto::kSystemBackend &&
-        !perfetto::test::StartSystemService()) {
-      GTEST_SKIP();
+    // service that was running.
+    if (GetParam() == perfetto::kSystemBackend) {
+      system_service_ = perfetto::test::SystemService::Start();
+      // If the system backend isn't supported, skip all system backend tests.
+      if (!system_service_.valid()) {
+        GTEST_SKIP();
+      }
     }
 
     EXPECT_FALSE(perfetto::Tracing::IsInitialized());
@@ -785,6 +791,7 @@
     return 0;
   }
 
+  perfetto::test::SystemService system_service_;
   std::map<std::string, TestDataSourceHandle> data_sources_;
   std::list<TestTracingSessionHandle> sessions_;  // Needs stable pointers.
 };
@@ -4952,10 +4959,50 @@
   EXPECT_EQ(it, trace.packet().end());
 }
 
+TEST_P(PerfettoApiTest, ActivateTriggers) {
+  // Create a new trace session without any data sources configured.
+  perfetto::TraceConfig cfg;
+  cfg.add_buffers()->set_size_kb(1024);
+  perfetto::TraceConfig::TriggerConfig* tr_cfg = cfg.mutable_trigger_config();
+  tr_cfg->set_trigger_mode(perfetto::TraceConfig::TriggerConfig::STOP_TRACING);
+  tr_cfg->set_trigger_timeout_ms(5000);
+  perfetto::TraceConfig::TriggerConfig::Trigger* trigger =
+      tr_cfg->add_triggers();
+  trigger->set_name("trigger1");
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+
+  perfetto::Tracing::ActivateTriggers({"trigger2", "trigger1"}, 10000);
+
+  bool done = false;
+  std::mutex mutex;
+  std::condition_variable cv;
+  std::unique_lock<std::mutex> lock(mutex);
+  tracing_session->get()->SetOnStopCallback([&]() {
+    std::lock_guard<std::mutex> inner_lock(mutex);
+    done = true;
+    cv.notify_one();
+  });
+  cv.wait(lock, [&done] { return done; });
+
+  std::vector<char> bytes = tracing_session->get()->ReadTraceBlocking();
+  perfetto::protos::gen::Trace parsed_trace;
+  ASSERT_TRUE(parsed_trace.ParseFromArray(bytes.data(), bytes.size()));
+  EXPECT_THAT(
+      parsed_trace,
+      Property(&perfetto::protos::gen::Trace::packet,
+               Contains(Property(
+                   &perfetto::protos::gen::TracePacket::trigger,
+                   Property(&perfetto::protos::gen::Trigger::trigger_name,
+                            "trigger1")))));
+}
+
 TEST(PerfettoApiInitTest, DisableSystemConsumer) {
   g_test_tracing_policy->should_allow_consumer_connection = true;
 
-  if (!perfetto::test::StartSystemService()) {
+  auto system_service = perfetto::test::SystemService::Start();
+  // If the system backend isn't supported, skip
+  if (!system_service.valid()) {
     GTEST_SKIP();
   }
 
diff --git a/src/tracing/test/api_test_support.cc b/src/tracing/test/api_test_support.cc
index a03135d..e67435c 100644
--- a/src/tracing/test/api_test_support.cc
+++ b/src/tracing/test/api_test_support.cc
@@ -16,6 +16,7 @@
 
 #include "src/tracing/test/api_test_support.h"
 
+#include "perfetto/base/compiler.h"
 #include "perfetto/base/proc_utils.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/temp_file.h"
@@ -45,38 +46,69 @@
     test_helper_.StartServiceIfRequired();
   }
 
+  void CleanEnv() { test_helper_.CleanEnv(); }
+
  private:
   perfetto::base::TestTaskRunner task_runner_;
   perfetto::TestHelper test_helper_;
 };
 
+InProcessSystemService* g_system_service = nullptr;
+
 }  // namespace
 
-bool StartSystemService() {
-  static InProcessSystemService* system_service;
-
+// static
+SystemService SystemService::Start() {
   // If there already was a system service running, make sure the new one is
   // running before tearing down the old one. This avoids a 1 second
   // reconnection delay between each test since the connection to the new
   // service succeeds immediately.
-  std::unique_ptr<InProcessSystemService> old_service(system_service);
-  system_service = new InProcessSystemService();
+  std::unique_ptr<InProcessSystemService> old_service(g_system_service);
+  if (old_service) {
+    old_service->CleanEnv();
+  }
+  g_system_service = new InProcessSystemService();
 
   // Tear down the service at process exit to make sure temporary files get
   // deleted.
   static bool cleanup_registered;
   if (!cleanup_registered) {
-    atexit([] { delete system_service; });
+    atexit([] { delete g_system_service; });
     cleanup_registered = true;
   }
-  return true;
+  SystemService ret;
+  ret.valid_ = true;
+  return ret;
+}
+
+void SystemService::Clean() {
+  if (valid_) {
+    if (g_system_service) {
+      // Does not really stop. We want to reuse the service in future tests. It
+      // is important to clean the environment variables, though.
+      g_system_service->CleanEnv();
+    }
+  }
+  valid_ = false;
 }
 #else   // !PERFETTO_BUILDFLAG(PERFETTO_IPC)
-bool StartSystemService() {
-  return false;
+// static
+SystemService SystemService::Start() {
+  return SystemService();
+}
+void SystemService::Clean() {
+  valid_ = false;
 }
 #endif  // !PERFETTO_BUILDFLAG(PERFETTO_IPC)
 
+SystemService& SystemService::operator=(SystemService&& other) noexcept {
+  PERFETTO_CHECK(!valid_ || !other.valid_);
+  Clean();
+  valid_ = other.valid_;
+  other.valid_ = false;
+  return *this;
+}
+
 int32_t GetCurrentProcessId() {
   return static_cast<int32_t>(base::GetProcessId());
 }
diff --git a/src/tracing/test/api_test_support.h b/src/tracing/test/api_test_support.h
index 33a7a53..0fe4cf2 100644
--- a/src/tracing/test/api_test_support.h
+++ b/src/tracing/test/api_test_support.h
@@ -27,13 +27,37 @@
 //  IMPORTANT: This header must not pull any non-public perfetto header.
 
 #include <stdint.h>
+
 #include "perfetto/tracing.h"
 
 namespace perfetto {
 namespace test {
 
 int32_t GetCurrentProcessId();
-bool StartSystemService();
+
+// RAII wrapper to start and stop an in process system service. Only one at a
+// time can be started.
+class SystemService {
+ public:
+  static SystemService Start();
+  SystemService() = default;
+  SystemService(SystemService&& other) noexcept { *this = std::move(other); }
+  SystemService& operator=(SystemService&&) noexcept;
+
+  ~SystemService() { Clean(); }
+
+  // Returns true if this SystemService has been started successfully and can be
+  // used.
+  bool valid() const { return valid_; }
+
+  void Clean();
+
+ private:
+  SystemService(const SystemService&) = delete;
+  SystemService& operator=(const SystemService&) = delete;
+  bool valid_ = false;
+};
+
 void SyncProducers();
 
 void SetBatchCommitsDuration(uint32_t batch_commits_duration_ms,
diff --git a/src/tracing/tracing.cc b/src/tracing/tracing.cc
index ab495cf..542bb26 100644
--- a/src/tracing/tracing.cc
+++ b/src/tracing/tracing.cc
@@ -84,6 +84,12 @@
       ->CreateTracingSession(backend);
 }
 
+//  static
+void Tracing::ActivateTriggers(const std::vector<std::string>& triggers,
+                               uint32_t ttl_ms) {
+  internal::TracingMuxer::Get()->ActivateTriggers(triggers, ttl_ms);
+}
+
 // Can be called from any thread.
 bool TracingSession::FlushBlocking(uint32_t timeout_ms) {
   std::atomic<bool> flush_result;
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index 3eef756..d066b23 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -37,6 +37,7 @@
   ],
   test_suites: [
     "cts",
+    "mts",
     "vts10",
     "general-tests",
   ],
@@ -59,6 +60,7 @@
         ":CtsPerfettoReporterApp"
   ],
   stl: "libc++_static",
+  min_sdk_version: "31",
   defaults: [
     "perfetto_defaults",
   ],
diff --git a/test/cts/AndroidTest.xml b/test/cts/AndroidTest.xml
index db14fe6..84e72bc 100644
--- a/test/cts/AndroidTest.xml
+++ b/test/cts/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsPerfettoProducerApp.apk" />
@@ -44,4 +45,17 @@
         <!-- test-timeout unit is ms -->
         <option name="native-test-timeout" value="40000" />
     </test>
+
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+         one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- ART Mainline Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.art" />
+        <!-- ART Mainline Module (external (AOSP) version). -->
+        <option name="mainline-module-package-name" value="com.android.art" />
+    </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12)
+         or above, where ART is an updatable Mainline module. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/test/cts/heapprofd_java_test_cts.cc b/test/cts/heapprofd_java_test_cts.cc
index 7d81128..6435f76 100644
--- a/test/cts/heapprofd_java_test_cts.cc
+++ b/test/cts/heapprofd_java_test_cts.cc
@@ -20,6 +20,8 @@
 #include <sys/wait.h>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/android_utils.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
 #include "test/android_test_utils.h"
@@ -34,6 +36,22 @@
 namespace perfetto {
 namespace {
 
+// Even though ART is a mainline module, there are dependencies on perfetto for
+// OOM heap dumps to work correctly.
+bool SupportsOomHeapDump() {
+  auto sdk = base::StringToInt32(base::GetAndroidProp("ro.build.version.sdk"));
+  if (sdk && *sdk >= 34) {
+    PERFETTO_LOG("SDK supports OOME heap dumps");
+    return true;
+  }
+  if (base::GetAndroidProp("ro.build.version.codename") == "UpsideDownCake") {
+    PERFETTO_LOG("Codename supports OOME heap dumps");
+    return true;
+  }
+  PERFETTO_LOG("OOME heap dumps not supported");
+  return false;
+}
+
 std::string RandomSessionName() {
   std::random_device rd;
   std::default_random_engine generator(rd());
@@ -89,6 +107,59 @@
   return helper.trace();
 }
 
+std::vector<protos::gen::TracePacket> TriggerOomHeapDump(std::string app_name,
+                                                         std::string heap_dump_target) {
+  base::TestTaskRunner task_runner;
+
+  // (re)start the target app's main activity
+  if (IsAppRunning(app_name)) {
+    StopApp(app_name, "old.app.stopped", &task_runner);
+    task_runner.RunUntilCheckpoint("old.app.stopped", 10000 /*ms*/);
+  }
+
+  // set up tracing
+  TestHelper helper(&task_runner);
+  helper.ConnectConsumer();
+  helper.WaitForConsumerConnect();
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(40 * 1024);
+  trace_config.set_unique_session_name(RandomSessionName().c_str());
+  trace_config.set_data_source_stop_timeout_ms(60000);
+
+  auto* trigger_config = trace_config.mutable_trigger_config();
+  trigger_config->set_trigger_mode(perfetto::protos::gen::TraceConfig::TriggerConfig::START_TRACING);
+  trigger_config->set_trigger_timeout_ms(60000);
+  auto* oom_trigger = trigger_config->add_triggers();
+  oom_trigger->set_name("com.android.telemetry.art-outofmemory");
+  oom_trigger->set_stop_delay_ms(10000);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.java_hprof.oom");
+  ds_config->set_target_buffer(0);
+
+  protos::gen::JavaHprofConfig java_hprof_config;
+  java_hprof_config.add_process_cmdline(heap_dump_target.c_str());
+  ds_config->set_java_hprof_config_raw(java_hprof_config.SerializeAsString());
+
+  // start tracing
+  helper.StartTracing(trace_config);
+  StartAppActivity(app_name, "JavaOomActivity", "target.app.running", &task_runner,
+                   /*delay_ms=*/100);
+  task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/);
+
+  if (SupportsOomHeapDump()) {
+    helper.WaitForTracingDisabled();
+    helper.ReadData();
+    helper.WaitForReadData();
+  }
+
+  PERFETTO_CHECK(IsAppRunning(app_name));
+  StopApp(app_name, "new.app.stopped", &task_runner);
+  task_runner.RunUntilCheckpoint("new.app.stopped", 10000 /*ms*/);
+  return helper.trace();
+}
+
 void AssertGraphPresent(std::vector<protos::gen::TracePacket> packets) {
   ASSERT_GT(packets.size(), 0u);
 
@@ -184,5 +255,37 @@
   AssertGraphPresent(packets);
 }
 
+TEST(HeapprofdJavaCtsTest, DebuggableAppOom) {
+  std::string app_name = "android.perfetto.cts.app.debuggable";
+  const auto& packets = TriggerOomHeapDump(app_name, "*");
+  if (SupportsOomHeapDump()) {
+    AssertGraphPresent(packets);
+  }
+}
+
+TEST(HeapprofdJavaCtsTest, ProfileableAppOom) {
+  std::string app_name = "android.perfetto.cts.app.profileable";
+  const auto& packets = TriggerOomHeapDump(app_name, "*");
+  if (SupportsOomHeapDump()) {
+    AssertGraphPresent(packets);
+  }
+}
+
+TEST(HeapprofdJavaCtsTest, ReleaseAppOom) {
+  std::string app_name = "android.perfetto.cts.app.release";
+  const auto& packets = TriggerOomHeapDump(app_name, "*");
+  if (IsUserBuild()) {
+    AssertNoProfileContents(packets);
+  } else if (SupportsOomHeapDump()) {
+    AssertGraphPresent(packets);
+  }
+}
+
+TEST(HeapprofdJavaCtsTest, DebuggableAppOomNotSelected) {
+  std::string app_name = "android.perfetto.cts.app.debuggable";
+  const auto& packets = TriggerOomHeapDump(app_name, "not.this.app");
+  AssertNoProfileContents(packets);
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/test/cts/producer/Android.bp b/test/cts/producer/Android.bp
index 1503a39..76d8a4e 100644
--- a/test/cts/producer/Android.bp
+++ b/test/cts/producer/Android.bp
@@ -33,6 +33,7 @@
     compile_multilib: "both",
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    min_sdk_version: "31",
     jni_libs: [
         "libperfettocts_jni",
     ],
diff --git a/test/cts/reporter/Android.bp b/test/cts/reporter/Android.bp
index ab2633b..39f2d89 100644
--- a/test/cts/reporter/Android.bp
+++ b/test/cts/reporter/Android.bp
@@ -27,4 +27,5 @@
     srcs: ["src/**/*.java"],
     platform_apis: true,
     privileged: true,
+    min_sdk_version: "31",
 }
diff --git a/test/cts/test_apps/Android.bp b/test/cts/test_apps/Android.bp
index 1172be5..a29caf3 100644
--- a/test/cts/test_apps/Android.bp
+++ b/test/cts/test_apps/Android.bp
@@ -35,6 +35,7 @@
     compile_multilib: "both",
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    min_sdk_version: "31",
     jni_libs: [
         "libperfettocts_native",
     ],
@@ -55,6 +56,7 @@
     compile_multilib: "both",
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    min_sdk_version: "31",
     jni_libs: [
         "libperfettocts_native",
     ],
@@ -75,6 +77,7 @@
     compile_multilib: "both",
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    min_sdk_version: "31",
     jni_libs: [
         "libperfettocts_native",
     ],
@@ -95,6 +98,7 @@
     compile_multilib: "both",
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    min_sdk_version: "31",
     jni_libs: [
         "libperfettocts_native",
     ],
diff --git a/test/cts/test_apps/AndroidManifest_debuggable.xml b/test/cts/test_apps/AndroidManifest_debuggable.xml
index cb746c1..291469e 100755
--- a/test/cts/test_apps/AndroidManifest_debuggable.xml
+++ b/test/cts/test_apps/AndroidManifest_debuggable.xml
@@ -58,5 +58,18 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
+        <activity
+          android:name="android.perfetto.cts.app.JavaOomActivity"
+          android:exported="true">
+        </activity>
+        <activity-alias
+          android:name="android.perfetto.cts.app.debuggable.JavaOomActivity"
+          android:targetActivity="android.perfetto.cts.app.JavaOomActivity"
+          android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
     </application>
 </manifest>
diff --git a/test/cts/test_apps/AndroidManifest_profileable.xml b/test/cts/test_apps/AndroidManifest_profileable.xml
index bac8a66..077fd95 100755
--- a/test/cts/test_apps/AndroidManifest_profileable.xml
+++ b/test/cts/test_apps/AndroidManifest_profileable.xml
@@ -59,6 +59,19 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
+        <activity
+          android:name="android.perfetto.cts.app.JavaOomActivity"
+          android:exported="true">
+        </activity>
+        <activity-alias
+          android:name="android.perfetto.cts.app.profileable.JavaOomActivity"
+          android:targetActivity="android.perfetto.cts.app.JavaOomActivity"
+          android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
     </application>
 </manifest>
 
diff --git a/test/cts/test_apps/AndroidManifest_release.xml b/test/cts/test_apps/AndroidManifest_release.xml
index 5bc0f5b..417a539 100755
--- a/test/cts/test_apps/AndroidManifest_release.xml
+++ b/test/cts/test_apps/AndroidManifest_release.xml
@@ -58,5 +58,18 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
+        <activity
+          android:name="android.perfetto.cts.app.JavaOomActivity"
+          android:exported="true">
+        </activity>
+        <activity-alias
+          android:name="android.perfetto.cts.app.release.JavaOomActivity"
+          android:targetActivity="android.perfetto.cts.app.JavaOomActivity"
+          android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
     </application>
 </manifest>
diff --git a/test/cts/test_apps/src/android/perfetto/cts/app/JavaOomActivity.java b/test/cts/test_apps/src/android/perfetto/cts/app/JavaOomActivity.java
new file mode 100644
index 0000000..f73c135
--- /dev/null
+++ b/test/cts/test_apps/src/android/perfetto/cts/app/JavaOomActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.perfetto.cts.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class JavaOomActivity extends Activity {
+    @Override
+    public void onCreate(Bundle state) {
+        super.onCreate(state);
+        new Thread(() -> {
+            try {
+                byte[] alloc = new byte[Integer.MAX_VALUE];
+            } catch (OutOfMemoryError e) {
+            }
+        }).start();
+    }
+}
diff --git a/test/test_helper.cc b/test/test_helper.cc
index 79c054a..b3839d5 100644
--- a/test/test_helper.cc
+++ b/test/test_helper.cc
@@ -112,7 +112,7 @@
 
 void TestHelper::StartServiceIfRequired() {
   if (mode_ == Mode::kStartDaemons)
-    service_thread_.Start();
+    env_cleaner_ = service_thread_.Start();
 }
 
 FakeProducer* TestHelper::ConnectFakeProducer() {
diff --git a/test/test_helper.h b/test/test_helper.h
index 7331c64..69dee23 100644
--- a/test/test_helper.h
+++ b/test/test_helper.h
@@ -64,6 +64,54 @@
 #endif
 }
 
+// Captures the values of some environment variables when constructed and
+// restores them when destroyed.
+class TestEnvCleaner {
+ public:
+  TestEnvCleaner() {}
+  TestEnvCleaner(std::initializer_list<const char*> env_vars) {
+    prev_state_.reserve(env_vars.size());
+    for (const char* name : env_vars) {
+      prev_state_.emplace_back();
+      Var& var = prev_state_.back();
+      var.name = name;
+      const char* prev_value = getenv(name);
+      if (prev_value) {
+        var.value.emplace(prev_value);
+      }
+    }
+  }
+  ~TestEnvCleaner() { Clean(); }
+
+  TestEnvCleaner(const TestEnvCleaner&) = delete;
+  TestEnvCleaner(TestEnvCleaner&& obj) noexcept { *this = std::move(obj); }
+  TestEnvCleaner& operator=(const TestEnvCleaner&) = delete;
+  TestEnvCleaner& operator=(TestEnvCleaner&& obj) noexcept {
+    PERFETTO_CHECK(prev_state_.empty());
+    this->prev_state_ = std::move(obj.prev_state_);
+    obj.prev_state_.clear();
+    return *this;
+  }
+
+  void Clean() {
+    for (const Var& var : prev_state_) {
+      if (var.value) {
+        base::SetEnv(var.name, *var.value);
+      } else {
+        base::UnsetEnv(var.name);
+      }
+    }
+    prev_state_.clear();
+  }
+
+ private:
+  struct Var {
+    const char* name;
+    base::Optional<std::string> value;
+  };
+  std::vector<Var> prev_state_;
+};
+
 // This is used only in daemon starting integrations tests.
 class ServiceThread {
  public:
@@ -77,7 +125,9 @@
     runner_->PostTaskAndWaitForTesting([this]() { svc_.reset(); });
   }
 
-  void Start() {
+  TestEnvCleaner Start() {
+    TestEnvCleaner env_cleaner(
+        {"PERFETTO_PRODUCER_SOCK_NAME", "PERFETTO_CONSUMER_SOCK_NAME"});
     runner_ = base::ThreadTaskRunner::CreateAndStart("perfetto.svc");
     runner_->PostTaskAndWaitForTesting([this]() {
       svc_ = ServiceIPCHost::CreateInstance(runner_->get());
@@ -98,6 +148,7 @@
                        producer_socket_.c_str(), consumer_socket_.c_str());
       }
     });
+    return env_cleaner;
   }
 
   base::ThreadTaskRunner* runner() { return runner_ ? &*runner_ : nullptr; }
@@ -293,6 +344,13 @@
   }
   const std::vector<protos::gen::TracePacket>& trace() { return trace_; }
 
+  // Some fixtures want to reuse a global TestHelper in different testcases
+  // without destroying and recreating it, but they still need to avoid
+  // polluting environment variables.
+  //
+  // This restores the previous environment variables.
+  void CleanEnv() { env_cleaner_.Clean(); }
+
  private:
   static uint64_t next_instance_num_;
   uint64_t instance_num_;
@@ -315,6 +373,8 @@
   ServiceThread service_thread_;
   FakeProducerThread fake_producer_thread_;
 
+  TestEnvCleaner env_cleaner_;
+
   std::unique_ptr<TracingService::ConsumerEndpoint> endpoint_;  // Keep last.
 };