Merge "Add @RequiresApi annotation to repeated fields write APIs in StatsEvent" into main
diff --git a/apex/tests/libstatspull/Android.bp b/apex/tests/libstatspull/Android.bp
index c3473e3..d3079e0 100644
--- a/apex/tests/libstatspull/Android.bp
+++ b/apex/tests/libstatspull/Android.bp
@@ -45,7 +45,7 @@
     compile_multilib: "both",
 }
 
-cc_library_shared {
+cc_test_library {
     name: "libstatspull_testhelper",
     srcs: ["jni/stats_pull_helper.cpp"],
     cflags: [
@@ -54,12 +54,12 @@
         "-Wthread-safety",
     ],
     shared_libs: [
-        "libbinder_ndk",
-        "statsd-aidl-ndk",
+        "libstatspull",
+        "libstatssocket",
     ],
+    header_libs: ["libnativehelper_header_only"],
     static_libs: [
         "libbase",
-        "libstatspull_private",
-        "libstatssocket_private",
     ],
+    test_for: ["com.android.os.statsd"],
 }
diff --git a/framework/Android.bp b/framework/Android.bp
index fe1a546..7ffa7b5 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -30,6 +30,9 @@
     srcs: [
         ":statslog-statsd-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
     visibility: [
         "//cts/hostsidetests/statsd/apps:__subpackages__",
         "//vendor:__subpackages__",
diff --git a/lib/libstatspull/Android.bp b/lib/libstatspull/Android.bp
index d10e7e3..28c0a70 100644
--- a/lib/libstatspull/Android.bp
+++ b/lib/libstatspull/Android.bp
@@ -77,20 +77,6 @@
     export_include_dirs: ["include"],
 }
 
-// ONLY USE IN TESTS.
-cc_library_static {
-    name: "libstatspull_private",
-    defaults: [
-        "libstatspull_defaults",
-    ],
-    cflags: [
-        "-DLIB_STATS_PULL_TESTS_FLAG",
-    ],
-    visibility: [
-        "//packages/modules/StatsD/apex/tests/libstatspull",
-    ],
-}
-
 // Note: These unit tests only test PullAtomMetadata and subscriptions
 // For full E2E tests of pullers, use LibStatsPullTests
 cc_test {
@@ -152,4 +138,5 @@
     ],
     require_root: true,
     min_sdk_version: "30",
+    test_for: ["com.android.os.statsd"],
 }
diff --git a/lib/libstatspull/stats_pull_atom_callback.cpp b/lib/libstatspull/stats_pull_atom_callback.cpp
index b3194c7..b880f0a 100644
--- a/lib/libstatspull/stats_pull_atom_callback.cpp
+++ b/lib/libstatspull/stats_pull_atom_callback.cpp
@@ -121,8 +121,6 @@
         // Convert stats_events into StatsEventParcels.
         std::vector<StatsEventParcel> parcels;
 
-        // Resolves fuzz build failure in b/161575591.
-#if defined(__ANDROID_APEX__) || defined(LIB_STATS_PULL_TESTS_FLAG)
         for (int i = 0; i < statsEventList.data.size(); i++) {
             size_t size;
             uint8_t* buffer = AStatsEvent_getBuffer(statsEventList.data[i], &size);
@@ -133,7 +131,6 @@
             p.buffer.assign(buffer, buffer + size);
             parcels.push_back(std::move(p));
         }
-#endif
 
         Status status = resultReceiver->pullFinished(atomTag, success, parcels);
         if (!status.isOk()) {
diff --git a/lib/libstatssocket/Android.bp b/lib/libstatssocket/Android.bp
index 36aa4db..fce6a75 100644
--- a/lib/libstatssocket/Android.bp
+++ b/lib/libstatssocket/Android.bp
@@ -74,18 +74,6 @@
     min_sdk_version: "30",
 }
 
-//TODO (b/149842105): Figure out if there is a better solution for this.
-cc_test_library {
-    name: "libstatssocket_private",
-    defaults: [
-        "libstatssocket_defaults",
-    ],
-    visibility: [
-        "//packages/modules/StatsD/apex/tests/libstatspull",
-        "//packages/modules/StatsD/statsd",
-    ],
-}
-
 cc_library_headers {
     name: "libstatssocket_headers",
     export_include_dirs: ["include"],
@@ -114,10 +102,10 @@
     static_libs: [
         "libbase",
         "libgmock",
-        "libstatssocket_private",
     ],
     shared_libs: [
         "libutils",
+        "libstatssocket",
     ],
     test_suites: [
         "device-tests",
@@ -137,6 +125,7 @@
     },
     require_root: true,
     min_sdk_version: "30",
+    test_for: ["com.android.os.statsd"],
 }
 
 genrule {
@@ -163,3 +152,29 @@
         "stats_statsdsocketlog.cpp",
     ],
 }
+
+cc_fuzz {
+    name: "statsevent_fuzzer",
+    defaults: [
+        "libstatssocket_defaults",
+    ],
+    srcs: [
+        "fuzzers/stats_event_fuzzer.cpp",
+    ],
+    local_include_dirs: [
+        "include",
+    ],
+    host_supported: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    fuzz_config: {
+        cc: [
+            "singhtejinder@google.com",
+            "sharaienko@google.com",
+        ],
+    },
+}
diff --git a/lib/libstatssocket/fuzzers/stats_event_fuzzer.cpp b/lib/libstatssocket/fuzzers/stats_event_fuzzer.cpp
new file mode 100644
index 0000000..975ecf7
--- /dev/null
+++ b/lib/libstatssocket/fuzzers/stats_event_fuzzer.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include "include/stats_event.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+
+    // This only tests the libstatssocket APIs.
+    // Since fuzzing is not supported across processes, it does not fuzz statsd.
+    // See statsd_fuzzer.
+
+    AStatsEvent* event = AStatsEvent_obtain();
+
+    AStatsEvent_setAtomId(event, fdp.ConsumeIntegral<int32_t>());
+    // atom-level annotation
+    AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+
+    while (fdp.remaining_bytes() > 0) {
+        AStatsEvent_writeInt32(event, fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeBool(event, fdp.ConsumeBool());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeFloat(event, fdp.ConsumeFloatingPoint<float>());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeInt64(event, fdp.ConsumeIntegral<int64_t>());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeString(event, fdp.ConsumeRandomLengthString().c_str());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+    }
+
+    AStatsEvent_write(event);
+    AStatsEvent_release(event);
+    return 0;
+}
diff --git a/lib/libstatssocket/tests/stats_buffer_writer_queue_test.cpp b/lib/libstatssocket/tests/stats_buffer_writer_queue_test.cpp
index 44224c5..008009b 100644
--- a/lib/libstatssocket/tests/stats_buffer_writer_queue_test.cpp
+++ b/lib/libstatssocket/tests/stats_buffer_writer_queue_test.cpp
@@ -24,10 +24,14 @@
 #include "utils.h"
 
 using testing::_;
+using testing::AnyNumber;
+using testing::DoAll;
 using testing::Return;
 using testing::StrictMock;
 
-constexpr static int WAIT_MS = 200;
+namespace {
+
+constexpr static int WAIT_MS = 100;
 
 static AStatsEvent* generateTestEvent() {
     AStatsEvent* event = AStatsEvent_obtain();
@@ -40,12 +44,13 @@
 class BasicBufferWriterQueueMock : public BufferWriterQueue {
 public:
     BasicBufferWriterQueueMock() = default;
-    MOCK_METHOD(bool, handleCommand, (const BasicBufferWriterQueueMock::Cmd& cmd),
-                (const override));
+    MOCK_METHOD(bool, handleCommand, (const BufferWriterQueue::Cmd& cmd), (const override));
 };
 
 typedef StrictMock<BasicBufferWriterQueueMock> BufferWriterQueueMock;
 
+}  // namespace
+
 TEST(StatsBufferWriterQueueTest, TestWriteSuccess) {
     AStatsEvent* event = generateTestEvent();
 
@@ -64,6 +69,9 @@
     EXPECT_TRUE(addedToQueue);
     // to yeld to the queue worker thread
     std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+    queue.drainQueue();
+    EXPECT_EQ(queue.getQueueSize(), 0);
 }
 
 TEST(StatsBufferWriterQueueTest, TestWriteOverflow) {
@@ -89,6 +97,9 @@
     EXPECT_FALSE(addedToQueue);
 
     EXPECT_EQ(queue.getQueueSize(), BufferWriterQueueMock::kQueueMaxSizeLimit);
+
+    queue.drainQueue();
+    EXPECT_EQ(queue.getQueueSize(), 0);
 }
 
 TEST(StatsBufferWriterQueueTest, TestSleepOnOverflow) {
@@ -104,12 +115,16 @@
     std::vector<int64_t> attemptsTs;
 
     BufferWriterQueueMock queue;
-    EXPECT_CALL(queue, handleCommand(_))
-            .WillRepeatedly([&attemptsTs](const BufferWriterQueueMock::Cmd&) {
-                // store timestamp for command handler invocations
-                attemptsTs.push_back(get_elapsed_realtime_ns());
-                return false;
-            });
+    ON_CALL(queue, handleCommand(_))
+            .WillByDefault(DoAll(
+                    [&attemptsTs](const BufferWriterQueue::Cmd&) {
+                        // store timestamp for command handler invocations
+                        attemptsTs.push_back(get_elapsed_realtime_ns());
+                        return false;
+                    },
+                    Return(false)));
+
+    EXPECT_CALL(queue, handleCommand(_)).Times(AnyNumber());
 
     // simulate failed write to stats socket to fill the queue
     for (int i = 0; i < BufferWriterQueueMock::kQueueMaxSizeLimit; i++) {
@@ -117,10 +132,13 @@
         EXPECT_TRUE(addedToQueue);
     }
     AStatsEvent_release(event);
-
     // to yeld to the queue worker thread
     std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_MS));
 
+    // to eliminate extra commands handling on the worker thread
+    queue.drainQueue();
+    EXPECT_EQ(queue.getQueueSize(), 0);
+
     EXPECT_GE(attemptsTs.size(), 2);
     for (int i = 0; i < attemptsTs.size() - 1; i++) {
         EXPECT_GE(attemptsTs[i + 1] - attemptsTs[i],
diff --git a/perfetto/README.md b/perfetto/README.md
new file mode 100644
index 0000000..765b743
--- /dev/null
+++ b/perfetto/README.md
@@ -0,0 +1,16 @@
+To push config to be used at device boot automatically
+
+```
+adb push perfetto/atrace.pbtxt /data/misc/perfetto-configs/boottrace.pbtxt
+adb shell setprop persist.debug.perfetto.boottrace 1
+```
+
+The output trace will be written at /data/misc/perfetto-traces/boottrace.perfetto-trace.
+The file will be removed before a new trace is started.
+
+```
+adb pull /data/misc/perfetto-traces/boottrace.perfetto-trace
+```
+
+# Links
+- https://perfetto.dev/docs/case-studies/android-boot-tracing
diff --git a/perfetto/atrace.pbtxt b/perfetto/atrace.pbtxt
new file mode 100644
index 0000000..e5c512c
--- /dev/null
+++ b/perfetto/atrace.pbtxt
@@ -0,0 +1,30 @@
+buffers: {
+    size_kb: 522240
+    fill_policy: DISCARD
+}
+buffers: {
+  size_kb: 8192
+  fill_policy: RING_BUFFER
+}
+data_sources: {
+    config {
+        name: "linux.process_stats"
+        target_buffer: 1
+        process_stats_config {
+            scan_all_processes_on_start: true
+        }
+    }
+}
+data_sources: {
+    config {
+        name: "linux.ftrace"
+        ftrace_config {
+            atrace_categories: "binder_driver"
+            atrace_categories: "binder_lock"
+            atrace_categories: "sm"
+            atrace_categories: "ss"
+            atrace_apps: "*"
+        }
+    }
+}
+duration_ms: 180000
diff --git a/service/java/com/android/server/stats/StatsCompanionService.java b/service/java/com/android/server/stats/StatsCompanionService.java
index 16e66fe..6e920c5 100644
--- a/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/service/java/com/android/server/stats/StatsCompanionService.java
@@ -755,7 +755,8 @@
             // Setup receiver for device reboots or shutdowns.
             filter = new IntentFilter(Intent.ACTION_REBOOT);
             filter.addAction(Intent.ACTION_SHUTDOWN);
-            mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null, null);
+            mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null,
+                    /* scheduler= */ mHandler);
 
             // Register listener for statsd_java properties updates.
             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_STATSD_JAVA,
diff --git a/service/java/com/android/server/stats/StatsManagerService.java b/service/java/com/android/server/stats/StatsManagerService.java
index 61a1c75..a25c91f 100644
--- a/service/java/com/android/server/stats/StatsManagerService.java
+++ b/service/java/com/android/server/stats/StatsManagerService.java
@@ -849,7 +849,6 @@
             }
         }
     }
-
     private static final int CHUNK_SIZE = 1024 * 64; // 64 kB
 
     /**
diff --git a/statsd/Android.bp b/statsd/Android.bp
index 193fa06..2199a5c 100644
--- a/statsd/Android.bp
+++ b/statsd/Android.bp
@@ -75,6 +75,7 @@
         "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/TrainInfoPuller.cpp",
+        "src/external/Uprobestats.cpp",
         "src/FieldValue.cpp",
         "src/flags/FlagProvider.cpp",
         "src/guardrail/StatsdStats.cpp",
@@ -149,6 +150,7 @@
         "libbinder_ndk",
         "libincident",
         "liblog",
+        "libstatssocket",
     ],
     header_libs: [
         "libgtest_prod_headers",
@@ -262,10 +264,6 @@
     },
     stl: "libc++_static",
 
-    shared_libs: [
-        "libstatssocket",
-    ],
-
     apex_available: [
         "com.android.os.statsd",
         "test_com.android.os.statsd",
@@ -297,7 +295,6 @@
     static_libs: [
         "libgmock",
         "libstatslog_statsdtest",
-        "libstatssocket_private",
     ],
     proto: {
         type: "lite",
@@ -458,6 +455,7 @@
     ],
 
     min_sdk_version: "30",
+    test_for: ["com.android.os.statsd"],
 }
 
 //#############################
@@ -493,6 +491,8 @@
         "libgtest",
         "libstats_test_utils",
     ],
+
+    test_for: ["com.android.os.statsd"],
 }
 
 // ====  java proto device library (for test only)  ==============================
@@ -589,6 +589,7 @@
     defaults: [
         "statsd_defaults",
         "service_fuzzer_defaults",
+        "fuzzer_disable_leaks",
     ],
     srcs: [
         "fuzzers/statsd_service_fuzzer.cpp",
@@ -604,6 +605,36 @@
         "-Wno-unused-parameter",
     ],
     fuzz_config: {
+        triage_assignee: "waghpawan@google.com",
+        cc: [
+            "singhtejinder@google.com",
+            "sharaienko@google.com",
+        ],
+    },
+    proto: {
+        type: "lite",
+        static: true,
+    },
+}
+
+cc_fuzz {
+    name: "statsd_fuzzer",
+    defaults: [
+        "statsd_defaults",
+    ],
+    srcs: [
+        "fuzzers/statsd_socket_data_fuzzer.cpp",
+    ],
+    shared_libs: [
+        "libstatssocket",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    fuzz_config: {
         cc: [
             "singhtejinder@google.com",
             "sharaienko@google.com",
diff --git a/statsd/fuzzers/statsd_socket_data_fuzzer.cpp b/statsd/fuzzers/statsd_socket_data_fuzzer.cpp
new file mode 100644
index 0000000..bfdfd8b
--- /dev/null
+++ b/statsd/fuzzers/statsd_socket_data_fuzzer.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "socket/StatsSocketListener.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void fuzzSocket(const uint8_t* data, size_t size) {
+    LogEventQueue queue(50000);
+    LogEventFilter filter;
+    filter.setFilteringEnabled(false);
+
+    StatsSocketListener::processSocketMessage((const char*)data, size, 0, 0, queue, filter);
+
+    StatsSocketListener::processStatsEventBuffer(data, size, 0, 0, queue, filter);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    using namespace android::os::statsd;
+
+    fuzzSocket(data, size);
+
+    return 0;
+}
diff --git a/statsd/src/FieldValue.cpp b/statsd/src/FieldValue.cpp
index aa129a4..92ed96d 100644
--- a/statsd/src/FieldValue.cpp
+++ b/statsd/src/FieldValue.cpp
@@ -60,6 +60,17 @@
     return false;
 }
 
+std::vector<Matcher> dedupFieldMatchers(const std::vector<Matcher>& fieldMatchers) {
+    std::vector<Matcher> dedupedFieldMatchers;
+    for (size_t i = 0; i < fieldMatchers.size(); i++) {
+        if (std::find(dedupedFieldMatchers.begin(), dedupedFieldMatchers.end(), fieldMatchers[i]) ==
+            dedupedFieldMatchers.end()) {
+            dedupedFieldMatchers.push_back(fieldMatchers[i]);
+        }
+    }
+    return dedupedFieldMatchers;
+}
+
 void translateFieldMatcher(int tag, const FieldMatcher& matcher, int depth, int* pos, int* mask,
                            std::vector<Matcher>* output) {
     if (depth > kMaxLogDepth) {
@@ -559,6 +570,14 @@
     return totalSize;
 }
 
+size_t getFieldValuesSizeV2(const std::vector<FieldValue>& fieldValues) {
+    size_t totalSize = 0;
+    for (const FieldValue& fieldValue : fieldValues) {
+        totalSize += fieldValue.getSizeV2();
+    }
+    return totalSize;
+}
+
 bool shouldKeepSample(const FieldValue& sampleFieldValue, int shardOffset, int shardCount) {
     int hashValue = 0;
     switch (sampleFieldValue.mValue.type) {
diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h
index 9b81007..22fa89b 100644
--- a/statsd/src/FieldValue.h
+++ b/statsd/src/FieldValue.h
@@ -371,7 +371,6 @@
 class Annotations {
 public:
     Annotations() {
-        setNested(true);  // Nested = true by default
     }
 
     // This enum stores where particular annotations can be found in the
@@ -449,6 +448,10 @@
         return mField.getSize() + mValue.getSize();
     }
 
+    size_t getSizeV2() const {
+        return mValue.getSize();
+    }
+
     Field mField;
     Value mValue;
     Annotations mAnnotations;
@@ -464,6 +467,8 @@
 /* returns uid if the field is uid field, or -1 if the field is not a uid field */
 int getUidIfExists(const FieldValue& value);
 
+std::vector<Matcher> dedupFieldMatchers(const std::vector<Matcher>& fieldMatchers);
+
 void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
 
 bool isAttributionUidField(const Field& field, const Value& value);
@@ -481,6 +486,9 @@
 // the size is computed at runtime using the actual contents stored in the FieldValue.
 size_t getSize(const std::vector<FieldValue>& fieldValues);
 
+// Same as getSize but does not compute the size of Field.
+size_t getFieldValuesSizeV2(const std::vector<FieldValue>& fieldValues);
+
 bool shouldKeepSample(const FieldValue& sampleFieldValue, int shardOffset, int shardCount);
 
 }  // namespace statsd
diff --git a/statsd/src/HashableDimensionKey.cpp b/statsd/src/HashableDimensionKey.cpp
index 837d9e9..03a4229 100644
--- a/statsd/src/HashableDimensionKey.cpp
+++ b/statsd/src/HashableDimensionKey.cpp
@@ -398,6 +398,21 @@
     return mStateValuesKey < that.getStateValuesKey();
 }
 
+size_t MetricDimensionKey::getSize(const bool usesNestedDimensions) const {
+    size_t dimensionKeySize = 0;
+    // Dimension/State values
+    if (usesNestedDimensions) {
+        // Assume nested dimension adds an additional atomTag + # of dimension fields
+        dimensionKeySize += sizeof(int32_t);
+        dimensionKeySize += sizeof(int32_t) * getDimensionKeyInWhat().getValues().size();
+    }
+    dimensionKeySize += getFieldValuesSizeV2(getDimensionKeyInWhat().getValues());
+    // Each state value has a atomId and group/value
+    dimensionKeySize += sizeof(int32_t) * getStateValuesKey().getValues().size();
+    dimensionKeySize += getFieldValuesSizeV2(getStateValuesKey().getValues());
+    return dimensionKeySize;
+}
+
 bool AtomDimensionKey::operator==(const AtomDimensionKey& that) const {
     return mAtomTag == that.getAtomTag() && mAtomFieldValues == that.getAtomFieldValues();
 };
diff --git a/statsd/src/HashableDimensionKey.h b/statsd/src/HashableDimensionKey.h
index 5753d98..4792e8d 100644
--- a/statsd/src/HashableDimensionKey.h
+++ b/statsd/src/HashableDimensionKey.h
@@ -135,6 +135,8 @@
 
     bool operator<(const MetricDimensionKey& that) const;
 
+    size_t getSize(const bool usesNestedDimensions) const;
+
 private:
     HashableDimensionKey mDimensionKeyInWhat;
     HashableDimensionKey mStateValuesKey;
diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp
index 333b167..95882d5 100644
--- a/statsd/src/StatsLogProcessor.cpp
+++ b/statsd/src/StatsLogProcessor.cpp
@@ -36,6 +36,7 @@
 #include "stats_util.h"
 #include "statslog_statsd.h"
 #include "storage/StorageManager.h"
+#include "utils/api_tracing.h"
 
 using namespace android;
 using android::base::StringPrintf;
@@ -73,6 +74,7 @@
 const int FIELD_ID_DUMP_REPORT_REASON = 8;
 const int FIELD_ID_STRINGS = 9;
 const int FIELD_ID_DATA_CORRUPTED_REASON = 11;
+const int FIELD_ID_ESTIMATED_DATA_BYTES = 12;
 
 // for ActiveConfigList
 const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1;
@@ -388,6 +390,7 @@
 }
 
 void StatsLogProcessor::OnLogEvent(LogEvent* event) {
+    ATRACE_CALL();
     OnLogEvent(event, getElapsedRealtimeNs());
 }
 
@@ -769,6 +772,8 @@
 
     std::set<string> str_set;
 
+    int64_t totalSize = it->second->byteSize();
+
     ProtoOutputStream tempProto;
     // First, fill in ConfigMetricsReport using current data on memory, which
     // starts from filling in StatsLogReport's.
@@ -803,7 +808,12 @@
     }
 
     // Data corrupted reason
-    writeDataCorruptedReasons(tempProto);
+    writeDataCorruptedReasons(tempProto, FIELD_ID_DATA_CORRUPTED_REASON,
+                              StatsdStats::getInstance().hasEventQueueOverflow(),
+                              StatsdStats::getInstance().hasSocketLoss());
+
+    // Estimated memory bytes
+    tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_DATA_BYTES, totalSize);
 
     flushProtoToBuffer(tempProto, buffer);
 
@@ -1104,8 +1114,11 @@
                                                MetricsManager& metricsManager) {
     int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
     auto lastCheckTime = mLastByteSizeTimes.find(key);
+    int64_t minCheckPeriodNs = metricsManager.useV2SoftMemoryCalculation()
+                                       ? StatsdStats::kMinByteSizeV2CheckPeriodNs
+                                       : StatsdStats::kMinByteSizeCheckPeriodNs;
     if (lastCheckTime != mLastByteSizeTimes.end()) {
-        if (elapsedRealtimeNs - lastCheckTime->second < StatsdStats::kMinByteSizeCheckPeriodNs) {
+        if (elapsedRealtimeNs - lastCheckTime->second < minCheckPeriodNs) {
             return;
         }
     }
@@ -1449,6 +1462,7 @@
 }
 
 void StatsLogProcessor::onStatsdInitCompleted(const int64_t elapsedTimeNs) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     VLOG("Received boot completed signal");
     for (const auto& it : mMetricsManagers) {
@@ -1505,17 +1519,6 @@
     mLogEventFilter->setAtomIds(std::move(allAtomIds), this);
 }
 
-void StatsLogProcessor::writeDataCorruptedReasons(ProtoOutputStream& proto) {
-    if (StatsdStats::getInstance().hasEventQueueOverflow()) {
-        proto.write(FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED | FIELD_ID_DATA_CORRUPTED_REASON,
-                    DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW);
-    }
-    if (StatsdStats::getInstance().hasSocketLoss()) {
-        proto.write(FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED | FIELD_ID_DATA_CORRUPTED_REASON,
-                    DATA_CORRUPTED_SOCKET_LOSS);
-    }
-}
-
 bool StatsLogProcessor::validateAppBreadcrumbEvent(const LogEvent& event) const {
     if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) {
         // Check that app breadcrumb reported fields are valid.
diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h
index c1c7dd6..c5faaf2 100644
--- a/statsd/src/StatsLogProcessor.h
+++ b/statsd/src/StatsLogProcessor.h
@@ -327,8 +327,6 @@
     /* Tells LogEventFilter about atom ids to parse */
     void updateLogEventFilterLocked() const;
 
-    void writeDataCorruptedReasons(ProtoOutputStream& proto);
-
     bool validateAppBreadcrumbEvent(const LogEvent& event) const;
 
     // Function used to send a broadcast so that receiver for the config key can call getData
@@ -485,6 +483,8 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithMultipleAggTypes);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithDefaultAggType);
 
     FRIEND_TEST(KllMetricE2eTest, TestInitWithKllFieldPositionALL);
 
diff --git a/statsd/src/StatsService.cpp b/statsd/src/StatsService.cpp
index 86c2de8..9cbd7b1 100644
--- a/statsd/src/StatsService.cpp
+++ b/statsd/src/StatsService.cpp
@@ -43,6 +43,7 @@
 #include "storage/StorageManager.h"
 #include "subscriber/SubscriberReporter.h"
 #include "utils/DbUtils.h"
+#include "utils/api_tracing.h"
 
 using namespace android;
 
@@ -230,10 +231,14 @@
 
     if (mEventQueue != nullptr) {
         mLogsReaderThread = std::make_unique<std::thread>([this] { readLogs(); });
+        if (mLogsReaderThread) {
+            pthread_setname_np(mLogsReaderThread->native_handle(), "statsd.reader");
+        }
     }
 }
 
 StatsService::~StatsService() {
+    ATRACE_CALL();
     if (mEventQueue != nullptr) {
         stopReadingLogs();
         mLogsReaderThread->join();
@@ -984,6 +989,7 @@
 }
 
 Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     // Parse fd into proto.
@@ -1001,6 +1007,7 @@
 Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version,
                                       const string& versionString, const string& installer,
                                       const vector<uint8_t>& certificateHash) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackage was called");
@@ -1011,6 +1018,7 @@
 }
 
 Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackageRemoved was called");
@@ -1026,6 +1034,7 @@
 }
 
 Status StatsService::informAlarmForSubscriberTriggeringFired() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informAlarmForSubscriberTriggeringFired was called");
@@ -1042,6 +1051,7 @@
 }
 
 Status StatsService::informPollAlarmFired() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informPollAlarmFired was called");
@@ -1051,6 +1061,7 @@
 }
 
 Status StatsService::systemRunning() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     // When system_server is up and running, schedule the dropbox task to run.
@@ -1060,6 +1071,7 @@
 }
 
 Status StatsService::informDeviceShutdown() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::informDeviceShutdown");
     int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
@@ -1081,6 +1093,7 @@
 }
 
 Status StatsService::statsCompanionReady() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::statsCompanionReady was called");
@@ -1099,6 +1112,7 @@
 }
 
 Status StatsService::bootCompleted() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::bootCompleted was called");
@@ -1122,6 +1136,7 @@
 }
 
 void StatsService::Startup() {
+    ATRACE_CALL();
     mConfigManager->Startup();
     int64_t wallClockNs = getWallClockNs();
     int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
@@ -1131,6 +1146,7 @@
 }
 
 void StatsService::Terminate() {
+    ATRACE_CALL();
     ALOGI("StatsService::Terminating");
     if (mProcessor != nullptr) {
         int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
@@ -1151,6 +1167,7 @@
 }
 
 Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     getDataChecked(key, callingUid, output);
     return Status::ok();
@@ -1158,6 +1175,7 @@
 
 Status StatsService::getDataFd(int64_t key, const int32_t callingUid,
                                const ScopedFileDescriptor& fd) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     vector<uint8_t> reportData;
     getDataChecked(key, callingUid, &reportData);
@@ -1194,6 +1212,7 @@
 }
 
 Status StatsService::getMetadata(vector<uint8_t>* output) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
@@ -1202,6 +1221,7 @@
 
 Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
                                       const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     if (addConfigurationChecked(callingUid, key, config)) {
@@ -1225,6 +1245,7 @@
 
 Status StatsService::removeDataFetchOperation(int64_t key,
                                               const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     ConfigKey configKey(callingUid, key);
     mConfigManager->RemoveConfigReceiver(configKey);
@@ -1234,6 +1255,7 @@
 Status StatsService::setDataFetchOperation(int64_t key,
                                            const shared_ptr<IPendingIntentRef>& pir,
                                            const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     ConfigKey configKey(callingUid, key);
@@ -1249,6 +1271,7 @@
 Status StatsService::setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
                                                       const int32_t callingUid,
                                                       vector<int64_t>* output) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     mConfigManager->SetActiveConfigsChangedReceiver(callingUid, pir);
@@ -1261,6 +1284,7 @@
 }
 
 Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     mConfigManager->RemoveActiveConfigsChangedReceiver(callingUid);
@@ -1268,6 +1292,7 @@
 }
 
 Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     ConfigKey configKey(callingUid, key);
@@ -1279,6 +1304,7 @@
                                             int64_t subscriberId,
                                             const shared_ptr<IPendingIntentRef>& pir,
                                             const int32_t callingUid) {
+    ATRACE_CALL();
     VLOG("StatsService::setBroadcastSubscriber called.");
     ENFORCE_UID(AID_SYSTEM);
 
@@ -1296,6 +1322,7 @@
 Status StatsService::unsetBroadcastSubscriber(int64_t configId,
                                               int64_t subscriberId,
                                               const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::unsetBroadcastSubscriber called.");
@@ -1306,6 +1333,7 @@
 }
 
 Status StatsService::allPullersFromBootRegistered() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::allPullersFromBootRegistered was called");
@@ -1317,6 +1345,7 @@
                                               int64_t timeoutMillis,
                                               const std::vector<int32_t>& additiveFields,
                                               const shared_ptr<IPullAtomCallback>& pullerCallback) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::registerPullAtomCallback called.");
     mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis),
@@ -1329,6 +1358,7 @@
         int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis,
         const std::vector<int32_t>& additiveFields,
         const shared_ptr<IPullAtomCallback>& pullerCallback) {
+    ATRACE_CALL();
     if (!checkPermission(kPermissionRegisterPullAtom)) {
         return exception(
                 EX_SECURITY,
@@ -1344,6 +1374,7 @@
 }
 
 Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::unregisterPullAtomCallback called.");
     mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
@@ -1351,6 +1382,7 @@
 }
 
 Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
+    ATRACE_CALL();
     if (!checkPermission(kPermissionRegisterPullAtom)) {
         return exception(
                 EX_SECURITY,
@@ -1364,6 +1396,7 @@
 }
 
 Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     // TODO: add verifier permission
 
@@ -1393,6 +1426,7 @@
 }
 
 void StatsService::statsCompanionServiceDied(void* cookie) {
+    ATRACE_CALL();
     auto thiz = static_cast<StatsService*>(cookie);
     thiz->statsCompanionServiceDiedImpl();
 }
@@ -1431,6 +1465,7 @@
                                                           const shared_ptr<IPendingIntentRef>& pir,
                                                           const int32_t callingUid,
                                                           vector<int64_t>* output) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     if (!isAtLeastU()) {
         ALOGW("setRestrictedMetricsChangedOperation invoked on U- device");
@@ -1448,6 +1483,7 @@
 Status StatsService::removeRestrictedMetricsChangedOperation(const int64_t configId,
                                                              const string& configPackage,
                                                              const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     if (!isAtLeastU()) {
         ALOGW("removeRestrictedMetricsChangedOperation invoked on U- device");
@@ -1462,6 +1498,7 @@
                               const shared_ptr<IStatsQueryCallback>& callback,
                               const int64_t configKey, const string& configPackage,
                               const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     if (callback == nullptr) {
         ALOGW("querySql called with null callback.");
@@ -1477,6 +1514,7 @@
 
 Status StatsService::addSubscription(const vector<uint8_t>& subscriptionConfig,
                                      const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ATRACE_CALL();
     ENFORCE_SID(kTracedProbesSid);
 
     initShellSubscriber();
@@ -1486,6 +1524,7 @@
 }
 
 Status StatsService::removeSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ATRACE_CALL();
     ENFORCE_SID(kTracedProbesSid);
 
     if (mShellSubscriber != nullptr) {
@@ -1495,6 +1534,7 @@
 }
 
 Status StatsService::flushSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ATRACE_CALL();
     ENFORCE_SID(kTracedProbesSid);
 
     if (mShellSubscriber != nullptr) {
diff --git a/statsd/src/StatsService.h b/statsd/src/StatsService.h
index 3c34492..0a167dd 100644
--- a/statsd/src/StatsService.h
+++ b/statsd/src/StatsService.h
@@ -415,7 +415,7 @@
      */
     void onStatsdInitCompleted();
 
-    /**
+    /*
      *  This method is used to stop log reader thread.
      */
     void stopReadingLogs();
diff --git a/statsd/src/anomaly/AnomalyTracker.h b/statsd/src/anomaly/AnomalyTracker.h
index 4a4f7ff..d57a4b8 100644
--- a/statsd/src/anomaly/AnomalyTracker.h
+++ b/statsd/src/anomaly/AnomalyTracker.h
@@ -34,7 +34,6 @@
 
 using std::optional;
 using std::shared_ptr;
-using std::unordered_map;
 
 // Does NOT allow negative values.
 class AnomalyTracker : public virtual RefBase {
@@ -184,7 +183,7 @@
     // declared for that dimension) ends, in seconds. From this moment and onwards, anomalies
     // can be declared again.
     // Entries may be, but are not guaranteed to be, removed after the period is finished.
-    unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
+    std::unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
 
     // Advances mMostRecentBucketNum to bucketNum, deleting any data that is now too old.
     // Specifically, since it is now too old, removes the data for
diff --git a/statsd/src/anomaly/DurationAnomalyTracker.cpp b/statsd/src/anomaly/DurationAnomalyTracker.cpp
index f3bfc05..82e5bc8 100644
--- a/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -93,7 +93,7 @@
     // seldomly called. The alternative would be having InternalAlarms store information about the
     // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that
     // is rarely ever called.
-    unordered_map<MetricDimensionKey, sp<const InternalAlarm>> matchedAlarms;
+    std::unordered_map<MetricDimensionKey, sp<const InternalAlarm>> matchedAlarms;
     for (const auto& kv : mAlarms) {
         if (firedAlarms.count(kv.second) > 0) {
             matchedAlarms.insert({kv.first, kv.second});
diff --git a/statsd/src/anomaly/DurationAnomalyTracker.h b/statsd/src/anomaly/DurationAnomalyTracker.h
index ce06df0..73e741e 100644
--- a/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -23,8 +23,6 @@
 namespace os {
 namespace statsd {
 
-using std::unordered_map;
-
 class DurationAnomalyTracker : public virtual AnomalyTracker {
 public:
     DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey,
diff --git a/statsd/src/anomaly/subscriber_util.cpp b/statsd/src/anomaly/subscriber_util.cpp
index beeadcf..19d0ea5 100644
--- a/statsd/src/anomaly/subscriber_util.cpp
+++ b/statsd/src/anomaly/subscriber_util.cpp
@@ -20,6 +20,7 @@
 #include "subscriber_util.h"
 
 #include "external/Perfetto.h"
+#include "external/Uprobestats.h"
 #include "subscriber/IncidentdReporter.h"
 #include "subscriber/SubscriberReporter.h"
 
@@ -58,6 +59,11 @@
                     ALOGW("Failed to generate perfetto traces.");
                 }
                 break;
+            case Subscription::SubscriberInformationCase::kUprobestatsDetails:
+                if (!StartUprobeStats(subscription.uprobestats_details())) {
+                    ALOGW("Failed to start uprobestats.");
+                }
+                break;
             case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
                 SubscriberReporter::getInstance().alertBroadcastSubscriber(configKey, subscription,
                                                                            dimensionKey);
diff --git a/statsd/src/config/ConfigManager.h b/statsd/src/config/ConfigManager.h
index e881949..5a213b7 100644
--- a/statsd/src/config/ConfigManager.h
+++ b/statsd/src/config/ConfigManager.h
@@ -19,7 +19,9 @@
 #include <aidl/android/os/IPendingIntentRef.h>
 #include <stdio.h>
 
+#include <map>
 #include <mutex>
+#include <set>
 #include <string>
 
 #include "config/ConfigKey.h"
diff --git a/statsd/src/config/ConfigMetadataProvider.h b/statsd/src/config/ConfigMetadataProvider.h
new file mode 100644
index 0000000..451503a
--- /dev/null
+++ b/statsd/src/config/ConfigMetadataProvider.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+#pragma once
+
+#include <utils/RefBase.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class ConfigMetadataProvider : virtual public RefBase {
+public:
+    virtual ~ConfigMetadataProvider() {
+    }
+
+    virtual bool useV2SoftMemoryCalculation() = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/external/StatsPuller.cpp b/statsd/src/external/StatsPuller.cpp
index 0051b0c..e0fce5e 100644
--- a/statsd/src/external/StatsPuller.cpp
+++ b/statsd/src/external/StatsPuller.cpp
@@ -18,10 +18,12 @@
 #include "Log.h"
 
 #include "StatsPuller.h"
+
 #include "StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
 #include "puller_util.h"
 #include "stats_log_util.h"
+#include "utils/api_tracing.h"
 
 namespace android {
 namespace os {
@@ -44,6 +46,7 @@
 
 PullErrorCode StatsPuller::Pull(const int64_t eventTimeNs,
                                 std::vector<std::shared_ptr<LogEvent>>* data) {
+    ATRACE_CALL();
     lock_guard<std::mutex> lock(mLock);
     const int64_t elapsedTimeNs = getElapsedRealtimeNs();
     const int64_t systemUptimeMillis = getSystemUptimeMillis();
diff --git a/statsd/src/external/StatsPullerManager.cpp b/statsd/src/external/StatsPullerManager.cpp
index 4fb63a6..bba32b9 100644
--- a/statsd/src/external/StatsPullerManager.cpp
+++ b/statsd/src/external/StatsPullerManager.cpp
@@ -54,12 +54,14 @@
 
 bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
                               vector<shared_ptr<LogEvent>>* data) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     return PullLocked(tagId, configKey, eventTimeNs, data);
 }
 
 bool StatsPullerManager::Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
                               vector<std::shared_ptr<LogEvent>>* data) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     return PullLocked(tagId, uids, eventTimeNs, data);
 }
@@ -217,6 +219,7 @@
 }
 
 void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     int64_t wallClockNs = getWallClockNs();
 
@@ -231,8 +234,8 @@
                 // receiver to the list that will pull on this alarm.
                 // If pullNecessary is false, check if next pull time needs to be updated.
                 sp<PullDataReceiver> receiverPtr = receiverInfo.receiver.promote();
-                const bool pullNecessary = receiverPtr != nullptr && receiverPtr->isPullNeeded();
-                if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && pullNecessary) {
+                if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && receiverPtr != nullptr &&
+                    receiverPtr->isPullNeeded()) {
                     receivers.push_back(&receiverInfo);
                 } else {
                     if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
@@ -294,6 +297,7 @@
 }
 
 int StatsPullerManager::ForceClearPullerCache() {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     int totalCleared = 0;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
@@ -303,6 +307,7 @@
 }
 
 int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     int totalCleared = 0;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
@@ -315,6 +320,7 @@
                                                   const int64_t coolDownNs, const int64_t timeoutNs,
                                                   const vector<int32_t>& additiveFields,
                                                   const shared_ptr<IPullAtomCallback>& callback) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
 
@@ -339,6 +345,7 @@
 }
 
 void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     PullerKey key = {.uid = uid, .atomTag = atomTag};
     if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
diff --git a/statsd/src/external/Uprobestats.cpp b/statsd/src/external/Uprobestats.cpp
new file mode 100644
index 0000000..ad6613c
--- /dev/null
+++ b/statsd/src/external/Uprobestats.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#define STATSD_DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <private/android_filesystem_config.h>
+
+#include <string>
+
+#include "src/statsd_config.pb.h"  // Alert
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool StartUprobeStats(const UprobestatsDetails& config) {
+    // TODO: Add an implementation.
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/external/Uprobestats.h b/statsd/src/external/Uprobestats.h
new file mode 100644
index 0000000..e045e8f
--- /dev/null
+++ b/statsd/src/external/Uprobestats.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Starts the uprobestats process.
+bool StartUprobeStats(const UprobestatsDetails& config);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/guardrail/StatsdStats.cpp b/statsd/src/guardrail/StatsdStats.cpp
index 191ea3f..16354b7 100644
--- a/statsd/src/guardrail/StatsdStats.cpp
+++ b/statsd/src/guardrail/StatsdStats.cpp
@@ -63,6 +63,7 @@
 const int FIELD_ID_SUBSCRIPTION_STATS = 23;
 const int FIELD_ID_SOCKET_LOSS_STATS = 24;
 const int FIELD_ID_QUEUE_STATS = 25;
+const int FIELD_ID_SOCKET_READ_STATS = 26;
 
 const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CALLING_UID = 1;
 const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_ID = 2;
@@ -136,6 +137,7 @@
 const int FIELD_ID_DB_DELETION_TOO_OLD = 35;
 const int FIELD_ID_DB_DELETION_CONFIG_REMOVED = 36;
 const int FIELD_ID_DB_DELETION_CONFIG_UPDATED = 37;
+const int FIELD_ID_CONFIG_METADATA_PROVIDER_PROMOTION_FAILED = 38;
 
 const int FIELD_ID_INVALID_CONFIG_REASON_ENUM = 1;
 const int FIELD_ID_INVALID_CONFIG_REASON_METRIC_ID = 2;
@@ -200,13 +202,17 @@
 const int FIELD_ID_PER_SUBSCRIPTION_STATS_END_TIME = 5;
 const int FIELD_ID_PER_SUBSCRIPTION_STATS_FLUSH_COUNT = 6;
 
+// Socket read stats
+const int FIELD_ID_SOCKET_READ_STATS_BATCHED_READ_SIZE = 1;
+
 const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = {
         {util::BINDER_CALLS, {6000, 10000}},
         {util::LOOPER_STATS, {1500, 2500}},
         {util::CPU_TIME_PER_UID_FREQ, {6000, 10000}},
 };
 
-StatsdStats::StatsdStats() : mStatsdStatsId(rand()) {
+StatsdStats::StatsdStats()
+    : mStatsdStatsId(rand()), mSocketBatchReadHistogram(kNumBinsInSocketBatchReadHistogram) {
     mPushedAtomStats.resize(kMaxPushedAtomId + 1);
     mStartTimeSec = getWallClockSec();
 }
@@ -292,6 +298,28 @@
     mLogLossStats.emplace_back(wallClockTimeSec, count, lastError, lastTag, uid, pid);
 }
 
+void StatsdStats::noteBatchSocketRead(int32_t size) {
+    // Calculate the bin.
+    int bin = 0;
+    if (size < 0) {
+        ALOGE("Unexpected negative size read from socket. This should never happen");
+        bin = 0;
+    } else if (size < 5) {
+        bin = size;  // bin = [0,4].
+    } else if (size < 10) {
+        bin = 4 + (size / 5);  // bin = 5.
+    } else if (size < 100) {
+        bin = 5 + (size / 10);  // bin = [6,14].
+    } else if (size < 1000) {
+        bin = 14 + (size / 100);  // bin = [15-23].
+    } else if (size < 2000) {
+        bin = 19 + (size / 200);  // bin = [24-28].
+    } else {                      // 2000+
+        bin = 29;
+    }
+    lock_guard<std::mutex> lock(mLock);
+    mSocketBatchReadHistogram[bin] += 1;
+}
 void StatsdStats::noteBroadcastSent(const ConfigKey& key) {
     noteBroadcastSent(key, getWallClockSec());
 }
@@ -525,6 +553,16 @@
     it->second->db_deletion_config_updated++;
 }
 
+void StatsdStats::noteConfigMetadataProviderPromotionFailed(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->config_metadata_provider_promote_failure++;
+}
+
 void StatsdStats::noteUidMapDropped(int deltas) {
     lock_guard<std::mutex> lock(mLock);
     mUidMapStats.dropped_changes += mUidMapStats.dropped_changes + deltas;
@@ -1068,6 +1106,7 @@
         config.second->db_deletion_too_old = 0;
         config.second->db_deletion_config_removed = 0;
         config.second->db_deletion_config_updated = 0;
+        config.second->config_metadata_provider_promote_failure = 0;
     }
     for (auto& pullStats : mPulledAtomStats) {
         pullStats.second.totalPull = 0;
@@ -1100,6 +1139,7 @@
     mPushedAtomDropsStats.clear();
     mRestrictedMetricQueryStats.clear();
     mSubscriptionPullThreadWakeupCount = 0;
+    std::fill(mSocketBatchReadHistogram.begin(), mSocketBatchReadHistogram.end(), 0);
 
     for (auto it = mSubscriptionStats.begin(); it != mSubscriptionStats.end();) {
         if (it->second.end_time_sec > 0) {
@@ -1184,6 +1224,10 @@
                     configStats->db_deletion_too_old, configStats->db_deletion_config_removed,
                     configStats->db_deletion_config_updated);
         }
+        if (configStats->config_metadata_provider_promote_failure > 0) {
+            dprintf(out, "ConfigMetadataProviderPromotionFailure=%d",
+                    configStats->config_metadata_provider_promote_failure);
+        }
         dprintf(out, "\n");
         if (!configStats->is_valid) {
             dprintf(out, "\tinvalid config reason: %s\n",
@@ -1206,7 +1250,8 @@
         auto dropBytesPtr = configStats->data_drop_bytes.begin();
         for (int i = 0; i < (int)configStats->data_drop_time_sec.size();
              i++, dropTimePtr++, dropBytesPtr++) {
-            dprintf(out, "\tdata drop time: %d with size %lld", *dropTimePtr,
+            dprintf(out, "\tdata drop time: %s(%lld) with %lld bytes\n",
+                    buildTimeString(*dropTimePtr).c_str(), (long long)*dropTimePtr,
                     (long long)*dropBytesPtr);
         }
 
@@ -1412,6 +1457,28 @@
                 (long long)restart);
     }
 
+    dprintf(out, "********Socket batch read size stats***********\n");
+    for (int i = 0; i < kNumBinsInSocketBatchReadHistogram; i++) {
+        if (mSocketBatchReadHistogram[i] == 0) {
+            continue;
+        }
+        string range;
+        if (i < 5) {
+            range = "[" + to_string(i) + "]";
+        } else if (i == 5) {
+            range = "[5-9]";
+        } else if (i < 15) {
+            range = "[" + to_string(i - 5) + "0-" + to_string(i - 5) + "9]";
+        } else if (i < 24) {
+            range = "[" + to_string(i - 14) + "00-" + to_string(i - 14) + "99]";
+        } else if (i < 29) {
+            range = "[" + to_string((i - 19) * 2) + "00-" + to_string((i - 19) * 2 + 1) + "99]";
+        } else {
+            range = "[2000+]";
+        }
+        dprintf(out, "%s: %lld\n", range.c_str(), (long long)mSocketBatchReadHistogram[i]);
+    }
+
     for (const auto& loss : mLogLossStats) {
         dprintf(out,
                 "Log loss: %lld (wall clock sec) - %d (count), %d (last error), %d (last tag), %d "
@@ -1491,6 +1558,7 @@
     dprintf(out, "Statsd Stats Id %d\n", mStatsdStatsId);
     dprintf(out, "********Shard Offset Provider stats***********\n");
     dprintf(out, "Shard Offset: %u\n", ShardOffsetProvider::getInstance().getShardOffset());
+    dprintf(out, "\n");
 }
 
 void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* proto) {
@@ -1682,6 +1750,8 @@
                              configStats.db_deletion_config_removed, proto);
     writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_DB_DELETION_CONFIG_UPDATED,
                              configStats.db_deletion_config_updated, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_METADATA_PROVIDER_PROMOTION_FAILED,
+                             configStats.config_metadata_provider_promote_failure, proto);
     for (int64_t latency : configStats.total_flush_latency_ns) {
         proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_FLUSH_LATENCY |
                              FIELD_COUNT_REPEATED,
@@ -1930,6 +2000,16 @@
 
     proto.end(socketLossStatsToken);
 
+    // Socket batch read stats.
+    const uint64_t socketReadStatsToken =
+            proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_READ_STATS);
+    for (const auto& it : mSocketBatchReadHistogram) {
+        proto.write(FIELD_TYPE_INT64 | FIELD_ID_SOCKET_READ_STATS_BATCHED_READ_SIZE |
+                            FIELD_COUNT_REPEATED,
+                    it);
+    }
+    proto.end(socketReadStatsToken);
+
     output->clear();
     proto.serializeToVector(output);
 
diff --git a/statsd/src/guardrail/StatsdStats.h b/statsd/src/guardrail/StatsdStats.h
index e3ab443..03266ee 100644
--- a/statsd/src/guardrail/StatsdStats.h
+++ b/statsd/src/guardrail/StatsdStats.h
@@ -91,6 +91,8 @@
     int32_t db_deletion_too_old = 0;
     int32_t db_deletion_config_removed = 0;
     int32_t db_deletion_config_updated = 0;
+    // Stores the number of ConfigMetadataProvider promotion failures
+    int32_t config_metadata_provider_promote_failure = 0;
 
     // Stores reasons for why config is valid or not
     std::optional<InvalidConfigReason> reason;
@@ -180,8 +182,8 @@
     const static int kMaxConfigCountPerUid = 20;
     const static int kMaxAlertCountPerConfig = 200;
     const static int kMaxConditionCountPerConfig = 500;
-    const static int kMaxMetricCountPerConfig = 2000;
-    const static int kMaxMatcherCountPerConfig = 2500;
+    const static int kMaxMetricCountPerConfig = 3000;
+    const static int kMaxMatcherCountPerConfig = 3500;
 
     // The max number of old config stats we keep.
     const static int kMaxIceBoxSize = 20;
@@ -234,7 +236,11 @@
     static const int64_t kMinBroadcastPeriodNs = 60 * NS_PER_SEC;
 
     /* Min period between two checks of byte size per config key in nanoseconds. */
-    static const int64_t kMinByteSizeCheckPeriodNs = 60 * NS_PER_SEC;
+    static const int64_t kMinByteSizeCheckPeriodNs = 1 * 60 * NS_PER_SEC;
+
+    // Min period between two checks of byte size per config key in nanoseconds for V2 memory
+    // calculations.
+    static const int64_t kMinByteSizeV2CheckPeriodNs = 5 * 60 * NS_PER_SEC;
 
     /* Min period between two checks of restricted metrics TTLs. */
     static const int64_t kMinTtlCheckPeriodNs = 60 * 60 * NS_PER_SEC;
@@ -277,7 +283,7 @@
 
     // Maximum atom id value that we consider a platform pushed atom.
     // This should be updated once highest pushed atom id in atoms.proto approaches this value.
-    static const int kMaxPushedAtomId = 900;
+    static const int kMaxPushedAtomId = 1500;
 
     // Atom id that is the start of the pulled atoms.
     static const int kPullAtomStartTag = 10000;
@@ -301,6 +307,8 @@
 
     static const int32_t kMaxLoggedBucketDropEvents = 10;
 
+    static const int32_t kNumBinsInSocketBatchReadHistogram = 30;
+
     /**
      * Report a new config has been received and report the static stats about the config.
      *
@@ -386,6 +394,11 @@
     void noteDbDeletionConfigUpdated(const ConfigKey& key);
 
     /**
+     * Reports that the promotion for ConfigMetadataProvider failed.
+     */
+    void noteConfigMetadataProviderPromotionFailed(const ConfigKey& key);
+
+    /**
      * Report the size of output tuple of a condition.
      *
      * Note: only report when the condition has an output dimension, and the tuple
@@ -706,6 +719,8 @@
      */
     void noteSubscriptionPullThreadWakeup();
 
+    void noteBatchSocketRead(int32_t readSize);
+
     /**
      * Reset the historical stats. Including all stats in icebox, and the tracked stats about
      * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
@@ -936,6 +951,8 @@
 
     std::list<int32_t> mSystemServerRestartSec;
 
+    std::vector<int64_t> mSocketBatchReadHistogram;
+
     struct RestrictedMetricQueryStats {
         RestrictedMetricQueryStats(int32_t callingUid, int64_t configId,
                                    const string& configPackage, std::optional<int32_t> configUid,
@@ -1030,6 +1047,7 @@
     FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats);
     FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
     FRIEND_TEST(StatsdStatsTest, TestAtomSkippedStats);
+    FRIEND_TEST(StatsdStatsTest, TestConfigMetadataProviderPromotionFailed);
     FRIEND_TEST(StatsdStatsTest, TestConfigRemove);
     FRIEND_TEST(StatsdStatsTest, TestHasHitDimensionGuardrail);
     FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd);
@@ -1054,6 +1072,7 @@
     FRIEND_TEST(StatsdStatsTest, TestSystemServerCrash);
     FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold);
     FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd);
+    FRIEND_TEST(StatsdStatsTest, TestSocketBatchReadStats);
 };
 
 InvalidConfigReason createInvalidConfigReasonWithMatcher(const InvalidConfigReasonEnum reason,
diff --git a/statsd/src/guardrail/stats_log_enums.proto b/statsd/src/guardrail/stats_log_enums.proto
index d47bc80..c468d52 100644
--- a/statsd/src/guardrail/stats_log_enums.proto
+++ b/statsd/src/guardrail/stats_log_enums.proto
@@ -148,6 +148,11 @@
     INVALID_CONFIG_REASON_MATCHER_INVALID_VALUE_MATCHER_WITH_STRING_REPLACE = 90;
     INVALID_CONFIG_REASON_MATCHER_COMBINATION_WITH_STRING_REPLACE = 91;
     INVALID_CONFIG_REASON_MATCHER_STRING_REPLACE_WITH_NO_VALUE_MATCHER_WITH_POSITION_ANY = 92;
+    INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY = 93;
+    INVALID_CONFIG_REASON_GAUGE_METRIC_PUSHED_WITH_PULL_PROBABILITY = 94;
+    INVALID_CONFIG_REASON_GAUGE_METRIC_RANDOM_ONE_SAMPLE_WITH_PULL_PROBABILITY = 95;
+    INVALID_CONFIG_REASON_VALUE_METRIC_DEFINES_SINGLE_AND_MULTIPLE_AGG_TYPES = 96;
+    INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE = 97;
 };
 
 enum InvalidQueryReason {
diff --git a/statsd/src/logd/LogEvent.cpp b/statsd/src/logd/LogEvent.cpp
index d325186..94fe5cf 100644
--- a/statsd/src/logd/LogEvent.cpp
+++ b/statsd/src/logd/LogEvent.cpp
@@ -395,8 +395,10 @@
     int value = readNextValue<int32_t>();
     // should be one of predefined category in StatsLog.java
     switch (value) {
-        // Only diagnostic is currently supported for use.
         case ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC:
+        case ASTATSLOG_RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE:
+        case ASTATSLOG_RESTRICTION_CATEGORY_AUTHENTICATION:
+        case ASTATSLOG_RESTRICTION_CATEGORY_FRAUD_AND_ABUSE:
             break;
         default:
             mValid = false;
diff --git a/statsd/src/metrics/CountMetricProducer.cpp b/statsd/src/metrics/CountMetricProducer.cpp
index e7a331b..32be42e 100644
--- a/statsd/src/metrics/CountMetricProducer.cpp
+++ b/statsd/src/metrics/CountMetricProducer.cpp
@@ -54,6 +54,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 
 // for CountMetricDataWrapper
 const int FIELD_ID_DATA = 1;
@@ -73,13 +74,14 @@
         const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
         const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
         const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-                     stateGroupMap, getAppUpgradeBucketSplit(metric)),
+                     stateGroupMap, getAppUpgradeBucketSplit(metric), configMetadataProvider),
       mDimensionGuardrailHit(false),
       mDimensionHardLimit(
               StatsdStats::clampDimensionKeySizeLimit(metric.max_dimensions_per_bucket())) {
@@ -214,6 +216,7 @@
 
 void CountMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
     mPastBuckets.clear();
+    mTotalDataSize = 0;
 }
 
 void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
@@ -239,6 +242,8 @@
                            mDimensionGuardrailHit);
     }
 
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
 
@@ -314,6 +319,7 @@
     if (erase_data) {
         mPastBuckets.clear();
         mDimensionGuardrailHit = false;
+        mTotalDataSize = 0;
     }
 }
 
@@ -321,6 +327,7 @@
     flushIfNeededLocked(dropTimeNs);
     StatsdStats::getInstance().noteBucketDropped(mMetricId);
     mPastBuckets.clear();
+    mTotalDataSize = 0;
 }
 
 void CountMetricProducer::onConditionChangedLocked(const bool conditionMet,
@@ -455,7 +462,10 @@
         if (countPassesThreshold(counter.second)) {
             info.mCount = counter.second;
             auto& bucketList = mPastBuckets[counter.first];
+            const bool isFirstBucket = bucketList.empty();
             bucketList.push_back(info);
+            mTotalDataSize += computeBucketSizeLocked(eventTimeNs < fullBucketEndTimeNs,
+                                                      counter.first, isFirstBucket);
             VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId,
                  counter.first.toString().c_str(), (long long)counter.second);
         }
@@ -501,6 +511,11 @@
 // greater than actual data size as it contains each dimension of
 // CountMetricData is  duplicated.
 size_t CountMetricProducer::byteSizeLocked() const {
+    sp<ConfigMetadataProvider> configMetadataProvider = getConfigMetadataProvider();
+    if (configMetadataProvider != nullptr && configMetadataProvider->useV2SoftMemoryCalculation()) {
+        return computeOverheadSizeLocked(!mPastBuckets.empty(), mDimensionGuardrailHit) +
+               mTotalDataSize;
+    }
     size_t totalSize = 0;
     for (const auto& pair : mPastBuckets) {
         totalSize += pair.second.size() * kBucketSize;
@@ -508,6 +523,24 @@
     return totalSize;
 }
 
+// Estimate for the size of a CountBucket.
+size_t CountMetricProducer::computeBucketSizeLocked(const bool isFullBucket,
+                                                    const MetricDimensionKey& dimKey,
+                                                    const bool isFirstBucket) const {
+    size_t bucketSize =
+            MetricProducer::computeBucketSizeLocked(isFullBucket, dimKey, isFirstBucket);
+
+    // Count Value
+    bucketSize += sizeof(int32_t);
+
+    // ConditionTrueNanos
+    if (mConditionTrackerIndex >= 0 && mSlicedStateAtoms.empty() && !mConditionSliced) {
+        bucketSize += sizeof(int64_t);
+    }
+
+    return bucketSize;
+}
+
 void CountMetricProducer::onActiveStateChangedLocked(const int64_t eventTimeNs,
                                                      const bool isActive) {
     MetricProducer::onActiveStateChangedLocked(eventTimeNs, isActive);
diff --git a/statsd/src/metrics/CountMetricProducer.h b/statsd/src/metrics/CountMetricProducer.h
index 7fa463f..d34181c 100644
--- a/statsd/src/metrics/CountMetricProducer.h
+++ b/statsd/src/metrics/CountMetricProducer.h
@@ -47,6 +47,7 @@
             const ConfigKey& key, const CountMetric& countMetric, int conditionIndex,
             const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
             const uint64_t protoHash, int64_t timeBaseNs, int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
             const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                     eventDeactivationMap = {},
@@ -100,6 +101,9 @@
 
     void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;
 
+    size_t computeBucketSizeLocked(const bool isFullBucket, const MetricDimensionKey& dimKey,
+                                   const bool isFirstBucket) const override;
+
     optional<InvalidConfigReason> onConfigUpdatedLocked(
             const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp
index 9973a9e..b07f2e3 100644
--- a/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/statsd/src/metrics/DurationMetricProducer.cpp
@@ -53,6 +53,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 // for DurationMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for DurationMetricData
@@ -73,13 +74,14 @@
         const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting,
         const sp<ConditionWizard>& wizard, const uint64_t protoHash,
         const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-                     stateGroupMap, getAppUpgradeBucketSplit(metric)),
+                     stateGroupMap, getAppUpgradeBucketSplit(metric), configMetadataProvider),
       mAggregationType(metric.aggregation_type()),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
@@ -318,6 +320,7 @@
                                             const HashableDimensionKey& primaryKey,
                                             const FieldValue& oldState,
                                             const FieldValue& newState) {
+    std::lock_guard<std::mutex> lock(mMutex);
     // Check if this metric has a StateMap. If so, map the new state value to
     // the correct state group id.
     FieldValue newStateCopy = newState;
@@ -522,6 +525,9 @@
         return;
     }
 
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
+
     if (StatsdStats::getInstance().hasHitDimensionGuardrail(mMetricId)) {
         protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_DIMENSION_GUARDRAIL_HIT, true);
     }
@@ -841,8 +847,42 @@
                      eventTimeNs, values);
 }
 
+// Estimate for the size of a DurationBucket.
+size_t DurationMetricProducer::computeBucketSizeLocked(const bool isFullBucket,
+                                                       const MetricDimensionKey& dimKey,
+                                                       const bool isFirstBucket) const {
+    size_t bucketSize =
+            MetricProducer::computeBucketSizeLocked(isFullBucket, dimKey, isFirstBucket);
+
+    // Duration Value
+    bucketSize += sizeof(int64_t);
+
+    // ConditionTrueNanos
+    if (mConditionTrackerIndex >= 0 && mSlicedStateAtoms.empty() && !mConditionSliced) {
+        bucketSize += sizeof(int64_t);
+    }
+
+    return bucketSize;
+}
+
 size_t DurationMetricProducer::byteSizeLocked() const {
     size_t totalSize = 0;
+    sp<ConfigMetadataProvider> configMetadataProvider = getConfigMetadataProvider();
+    if (configMetadataProvider != nullptr && configMetadataProvider->useV2SoftMemoryCalculation()) {
+        bool hasHitDimensionGuardrail =
+                StatsdStats::getInstance().hasHitDimensionGuardrail(mMetricId);
+        totalSize += computeOverheadSizeLocked(!mPastBuckets.empty(), hasHitDimensionGuardrail);
+        for (const auto& pair : mPastBuckets) {
+            bool isFirstBucket = true;
+            for (const auto& bucket : pair.second) {
+                bool isFullBucket = bucket.mBucketEndNs - bucket.mBucketStartNs >= mBucketSizeNs;
+                totalSize +=
+                        computeBucketSizeLocked(isFullBucket, /*dimKey=*/pair.first, isFirstBucket);
+                isFirstBucket = false;
+            }
+        }
+        return totalSize;
+    }
     for (const auto& pair : mPastBuckets) {
         totalSize += pair.second.size() * kBucketSize;
     }
diff --git a/statsd/src/metrics/DurationMetricProducer.h b/statsd/src/metrics/DurationMetricProducer.h
index 2f3d859..c5403ff 100644
--- a/statsd/src/metrics/DurationMetricProducer.h
+++ b/statsd/src/metrics/DurationMetricProducer.h
@@ -44,6 +44,7 @@
             const int startIndex, int stopIndex, int stopAllIndex, const bool nesting,
             const sp<ConditionWizard>& wizard, const uint64_t protoHash,
             const FieldMatcher& internalDimensions, int64_t timeBaseNs, const int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {},
             const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {},
             const vector<int>& slicedStateAtoms = {},
@@ -138,6 +139,9 @@
     void addAnomalyTrackerLocked(sp<AnomalyTracker>& anomalyTracker,
                                  const UpdateStatus& updateStatus, int64_t updateTimeNs);
 
+    size_t computeBucketSizeLocked(const bool isFullBucket, const MetricDimensionKey& dimKey,
+                                   const bool isFirstBucket) const override;
+
     const DurationMetric_AggregationType mAggregationType;
 
     // Index of the SimpleAtomMatcher which defines the start.
diff --git a/statsd/src/metrics/EventMetricProducer.cpp b/statsd/src/metrics/EventMetricProducer.cpp
index aabd2b3..540e3d0 100644
--- a/statsd/src/metrics/EventMetricProducer.cpp
+++ b/statsd/src/metrics/EventMetricProducer.cpp
@@ -48,6 +48,9 @@
 const int FIELD_ID_ID = 1;
 const int FIELD_ID_EVENT_METRICS = 4;
 const int FIELD_ID_IS_ACTIVE = 14;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
+const int FIELD_ID_DATA_CORRUPTED_REASON = 19;
+
 // for EventMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for EventMetricData
@@ -60,13 +63,14 @@
         const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
         const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
         const uint64_t protoHash, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-                     stateGroupMap, /*splitBucketForAppUpgrade=*/nullopt),
+                     stateGroupMap, /*splitBucketForAppUpgrade=*/nullopt, configMetadataProvider),
       mSamplingPercentage(metric.sampling_percentage()) {
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
@@ -79,7 +83,6 @@
         mConditionSliced = true;
     }
 
-    mTotalSize = 0;
     VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)mMetricId,
          (long long)mBucketSizeNs, (long long)mTimeBaseNs);
 }
@@ -135,7 +138,9 @@
 
 void EventMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
     mAggregatedAtoms.clear();
-    mTotalSize = 0;
+    mDataCorruptedDueToSocketLoss = false;
+    mDataCorruptedDueToQueueOverflow = false;
+    mTotalDataSize = 0;
     StatsdStats::getInstance().noteBucketDropped(mMetricId);
 }
 
@@ -162,7 +167,9 @@
 
 void EventMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
     mAggregatedAtoms.clear();
-    mTotalSize = 0;
+    mDataCorruptedDueToSocketLoss = false;
+    mDataCorruptedDueToQueueOverflow = false;
+    mTotalDataSize = 0;
 }
 
 void EventMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
@@ -173,6 +180,13 @@
                                              ProtoOutputStream* protoOutput) {
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
+    // Data corrupted reason
+    writeDataCorruptedReasons(*protoOutput, FIELD_ID_DATA_CORRUPTED_REASON,
+                              mDataCorruptedDueToQueueOverflow, mDataCorruptedDueToSocketLoss);
+    if (!mAggregatedAtoms.empty()) {
+        protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                           (long long)byteSizeLocked());
+    }
     uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS);
     for (const auto& [atomDimensionKey, elapsedTimestampsNs] : mAggregatedAtoms) {
         uint64_t wrapperToken =
@@ -192,10 +206,13 @@
         protoOutput->end(aggregatedToken);
         protoOutput->end(wrapperToken);
     }
+
     protoOutput->end(protoToken);
     if (erase_data) {
         mAggregatedAtoms.clear();
-        mTotalSize = 0;
+        mDataCorruptedDueToSocketLoss = false;
+        mDataCorruptedDueToQueueOverflow = false;
+        mTotalDataSize = 0;
     }
 }
 
@@ -222,14 +239,24 @@
 
     std::vector<int64_t>& aggregatedTimestampsNs = mAggregatedAtoms[key];
     if (aggregatedTimestampsNs.empty()) {
-        mTotalSize += getSize(key.getAtomFieldValues().getValues());
+        sp<ConfigMetadataProvider> provider = getConfigMetadataProvider();
+        if (provider != nullptr && provider->useV2SoftMemoryCalculation()) {
+            mTotalDataSize += getFieldValuesSizeV2(key.getAtomFieldValues().getValues());
+        } else {
+            mTotalDataSize += getSize(key.getAtomFieldValues().getValues());
+        }
     }
     aggregatedTimestampsNs.push_back(elapsedTimeNs);
-    mTotalSize += sizeof(int64_t); // Add the size of the event timestamp
+    mTotalDataSize += sizeof(int64_t);  // Add the size of the event timestamp
 }
 
 size_t EventMetricProducer::byteSizeLocked() const {
-    return mTotalSize;
+    sp<ConfigMetadataProvider> provider = getConfigMetadataProvider();
+    if (provider != nullptr && provider->useV2SoftMemoryCalculation()) {
+        return mTotalDataSize +
+               computeOverheadSizeLocked(/*hasPastBuckets=*/false, /*dimensionGuardrailHit=*/false);
+    }
+    return mTotalDataSize;
 }
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/EventMetricProducer.h b/statsd/src/metrics/EventMetricProducer.h
index 6e02659..d64552b 100644
--- a/statsd/src/metrics/EventMetricProducer.h
+++ b/statsd/src/metrics/EventMetricProducer.h
@@ -38,6 +38,7 @@
             const ConfigKey& key, const EventMetric& eventMetric, int conditionIndex,
             const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
             const uint64_t protoHash, int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
             const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                     eventDeactivationMap = {},
@@ -50,9 +51,6 @@
         return METRIC_TYPE_EVENT;
     }
 
-protected:
-    size_t mTotalSize;
-
 private:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
diff --git a/statsd/src/metrics/GaugeMetricProducer.cpp b/statsd/src/metrics/GaugeMetricProducer.cpp
index 10a02d6..979b344 100644
--- a/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -50,6 +50,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 // for GaugeMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
@@ -80,12 +81,14 @@
         const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int triggerAtomId,
         const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs,
         const sp<StatsPullerManager>& pullerManager,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const size_t dimensionSoftLimit, const size_t dimensionHardLimit)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, /*slicedStateAtoms=*/{},
-                     /*stateGroupMap=*/{}, getAppUpgradeBucketSplit(metric)),
+                     /*stateGroupMap=*/{}, getAppUpgradeBucketSplit(metric),
+                     configMetadataProvider),
       mWhatMatcherIndex(whatMatcherIndex),
       mEventMatcherWizard(matcherWizard),
       mPullerManager(pullerManager),
@@ -101,7 +104,8 @@
       mDimensionHardLimit(dimensionHardLimit),
       mGaugeAtomsPerDimensionLimit(metric.max_num_gauge_atoms_per_bucket()),
       mDimensionGuardrailHit(false),
-      mSamplingPercentage(metric.sampling_percentage()) {
+      mSamplingPercentage(metric.sampling_percentage()),
+      mPullProbability(metric.pull_probability()) {
     mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
     mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
     int64_t bucketSizeMills = 0;
@@ -240,6 +244,7 @@
     flushIfNeededLocked(dumpTimeNs);
     mPastBuckets.clear();
     mSkippedBuckets.clear();
+    mTotalDataSize = 0;
 }
 
 void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
@@ -262,6 +267,9 @@
         return;
     }
 
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
+
     if (mDimensionGuardrailHit) {
         protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_DIMENSION_GUARDRAIL_HIT,
                            mDimensionGuardrailHit);
@@ -367,6 +375,7 @@
         mPastBuckets.clear();
         mSkippedBuckets.clear();
         mDimensionGuardrailHit = false;
+        mTotalDataSize = 0;
     }
 }
 
@@ -394,7 +403,7 @@
         default:
             break;
     }
-    if (!triggerPuller) {
+    if (!triggerPuller || !shouldKeepRandomSample(mPullProbability)) {
         return;
     }
     vector<std::shared_ptr<LogEvent>> allData;
@@ -620,6 +629,7 @@
     flushIfNeededLocked(dropTimeNs);
     StatsdStats::getInstance().noteBucketDropped(mMetricId);
     mPastBuckets.clear();
+    mTotalDataSize = 0;
 }
 
 // When a new matched event comes in, we check if event falls into the current
@@ -667,7 +677,11 @@
                 elapsedTimestampsNs.push_back(atom.mElapsedTimestampNs);
             }
             auto& bucketList = mPastBuckets[slice.first];
+            const bool isFirstBucket = bucketList.empty();
             bucketList.push_back(info);
+            mTotalDataSize += computeGaugeBucketSizeLocked(eventTimeNs >= fullBucketEndTimeNs,
+                                                           /*dimKey=*/slice.first, isFirstBucket,
+                                                           info.mAggregatedAtoms);
             VLOG("Gauge gauge metric %lld, dump key value: %s", (long long)mMetricId,
                  slice.first.toString().c_str());
         }
@@ -679,6 +693,7 @@
                     buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL));
         }
         mSkippedBuckets.emplace_back(mCurrentSkippedBucket);
+        mTotalDataSize += computeSkippedBucketSizeLocked(mCurrentSkippedBucket);
     }
 
     // If we have anomaly trackers, we need to update the partial bucket values.
@@ -702,7 +717,29 @@
     mHasHitGuardrail = false;
 }
 
+// Estimate for the size of a GaugeBucket.
+size_t GaugeMetricProducer::computeGaugeBucketSizeLocked(
+        const bool isFullBucket, const MetricDimensionKey& dimKey, const bool isFirstBucket,
+        const std::unordered_map<AtomDimensionKey, std::vector<int64_t>>& aggregatedAtoms) const {
+    size_t bucketSize =
+            MetricProducer::computeBucketSizeLocked(isFullBucket, dimKey, isFirstBucket);
+
+    // Gauge Atoms and timestamps
+    for (const auto& pair : aggregatedAtoms) {
+        bucketSize += getFieldValuesSizeV2(pair.first.getAtomFieldValues().getValues());
+        bucketSize += sizeof(int64_t) * pair.second.size();
+    }
+
+    return bucketSize;
+}
+
 size_t GaugeMetricProducer::byteSizeLocked() const {
+    sp<ConfigMetadataProvider> configMetadataProvider = getConfigMetadataProvider();
+    if (configMetadataProvider != nullptr && configMetadataProvider->useV2SoftMemoryCalculation()) {
+        return computeOverheadSizeLocked(!mPastBuckets.empty() || !mSkippedBuckets.empty(),
+                                         mDimensionGuardrailHit) +
+               mTotalDataSize;
+    }
     size_t totalSize = 0;
     for (const auto& pair : mPastBuckets) {
         for (const auto& bucket : pair.second) {
diff --git a/statsd/src/metrics/GaugeMetricProducer.h b/statsd/src/metrics/GaugeMetricProducer.h
index 7c737a4..daff08c 100644
--- a/statsd/src/metrics/GaugeMetricProducer.h
+++ b/statsd/src/metrics/GaugeMetricProducer.h
@@ -66,6 +66,7 @@
             const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard,
             const int pullTagId, int triggerAtomId, int atomId, const int64_t timeBaseNs,
             int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
             const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                     eventDeactivationMap = {},
@@ -81,7 +82,8 @@
     // Determine if metric needs to pull
     bool isPullNeeded() const override {
         std::lock_guard<std::mutex> lock(mMutex);
-        return mIsActive && (mCondition == ConditionState::kTrue);
+        return mIsActive && (mCondition == ConditionState::kTrue) &&
+               shouldKeepRandomSample(mPullProbability);
     };
 
     // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
@@ -94,8 +96,8 @@
 
     // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
     void onStatsdInitCompleted(int64_t eventTimeNs) override {
+        ATRACE_CALL();
         std::lock_guard<std::mutex> lock(mMutex);
-
         flushLocked(eventTimeNs);
         if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE && mIsActive) {
             pullAndMatchEventsLocked(eventTimeNs);
@@ -147,6 +149,11 @@
     // Only call if mCondition == ConditionState::kTrue && metric is active.
     void pullAndMatchEventsLocked(const int64_t timestampNs);
 
+    size_t computeGaugeBucketSizeLocked(
+            const bool isFullBucket, const MetricDimensionKey& dimKey, const bool isFirstBucket,
+            const std::unordered_map<AtomDimensionKey, std::vector<int64_t>>& aggregatedAtoms)
+            const;
+
     optional<InvalidConfigReason> onConfigUpdatedLocked(
             const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
@@ -227,6 +234,8 @@
 
     const int mSamplingPercentage;
 
+    const int mPullProbability;
+
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition);
diff --git a/statsd/src/metrics/KllMetricProducer.cpp b/statsd/src/metrics/KllMetricProducer.cpp
index 8e50567..cc59e3c 100644
--- a/statsd/src/metrics/KllMetricProducer.cpp
+++ b/statsd/src/metrics/KllMetricProducer.cpp
@@ -58,9 +58,11 @@
                                      const ConditionOptions& conditionOptions,
                                      const StateOptions& stateOptions,
                                      const ActivationOptions& activationOptions,
-                                     const GuardrailOptions& guardrailOptions)
+                                     const GuardrailOptions& guardrailOptions,
+                                     const wp<ConfigMetadataProvider> configMetadataProvider)
     : ValueMetricProducer(metric.id(), key, protoHash, pullOptions, bucketOptions, whatOptions,
-                          conditionOptions, stateOptions, activationOptions, guardrailOptions) {
+                          conditionOptions, stateOptions, activationOptions, guardrailOptions,
+                          configMetadataProvider) {
 }
 
 KllMetricProducer::DumpProtoFields KllMetricProducer::getDumpProtoFields() const {
@@ -151,7 +153,26 @@
     return bucket;
 }
 
+// Estimate for the size of NumericValues.
+size_t KllMetricProducer::getAggregatedValueSize(const std::unique_ptr<KllQuantile>& kll) const {
+    size_t valueSize = 0;
+    // Index
+    valueSize += sizeof(int32_t);
+
+    // Value
+    valueSize += kll->SerializeToProto().ByteSizeLong();
+
+    return valueSize;
+}
+
 size_t KllMetricProducer::byteSizeLocked() const {
+    sp<ConfigMetadataProvider> configMetadataProvider = getConfigMetadataProvider();
+    if (configMetadataProvider != nullptr && configMetadataProvider->useV2SoftMemoryCalculation()) {
+        bool dimensionGuardrailHit = StatsdStats::getInstance().hasHitDimensionGuardrail(mMetricId);
+        return computeOverheadSizeLocked(!mPastBuckets.empty() || !mSkippedBuckets.empty(),
+                                         dimensionGuardrailHit) +
+               mTotalDataSize;
+    }
     size_t totalSize = 0;
     for (const auto& [_, buckets] : mPastBuckets) {
         totalSize += buckets.size() * kBucketSize;
diff --git a/statsd/src/metrics/KllMetricProducer.h b/statsd/src/metrics/KllMetricProducer.h
index 43ca6a4..dd5dcbd 100644
--- a/statsd/src/metrics/KllMetricProducer.h
+++ b/statsd/src/metrics/KllMetricProducer.h
@@ -48,7 +48,8 @@
                       const PullOptions& pullOptions, const BucketOptions& bucketOptions,
                       const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
                       const StateOptions& stateOptions, const ActivationOptions& activationOptions,
-                      const GuardrailOptions& guardrailOptions);
+                      const GuardrailOptions& guardrailOptions,
+                      const wp<ConfigMetadataProvider> configMetadataProvider);
 
     inline MetricType getMetricType() const override {
         return METRIC_TYPE_KLL;
@@ -102,6 +103,8 @@
                                          const int sampleSize,
                                          ProtoOutputStream* const protoOutput) const override;
 
+    size_t getAggregatedValueSize(const std::unique_ptr<KllQuantile>& kll) const override;
+
     bool aggregateFields(const int64_t eventTimeNs, const MetricDimensionKey& eventKey,
                          const LogEvent& event, std::vector<Interval>& intervals,
                          Empty& empty) override;
diff --git a/statsd/src/metrics/MetricProducer.cpp b/statsd/src/metrics/MetricProducer.cpp
index 55c9adf..ac80a20 100644
--- a/statsd/src/metrics/MetricProducer.cpp
+++ b/statsd/src/metrics/MetricProducer.cpp
@@ -34,7 +34,6 @@
 namespace os {
 namespace statsd {
 
-
 // for ActiveMetric
 const int FIELD_ID_ACTIVE_METRIC_ID = 1;
 const int FIELD_ID_ACTIVE_METRIC_ACTIVATION = 2;
@@ -53,7 +52,8 @@
                 eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap,
-        const optional<bool> splitBucketForAppUpgrade)
+        const optional<bool> splitBucketForAppUpgrade,
+        const wp<ConfigMetadataProvider> configMetadataProvider)
     : mMetricId(metricId),
       mProtoHash(protoHash),
       mConfigKey(key),
@@ -78,7 +78,8 @@
       mSplitBucketForAppUpgrade(splitBucketForAppUpgrade),
       mHasHitGuardrail(false),
       mSampledWhatFields({}),
-      mShardCount(0) {
+      mShardCount(0),
+      mConfigMetadataProvider(configMetadataProvider) {
 }
 
 optional<InvalidConfigReason> MetricProducer::onConfigUpdatedLocked(
@@ -185,6 +186,11 @@
                                     statePrimaryKeys);
 }
 
+void MetricProducer::onMatchedLogEventLostLocked(int32_t /*atomId*/, DataCorruptedReason reason) {
+    mDataCorruptedDueToSocketLoss |= reason == DATA_CORRUPTED_SOCKET_LOSS;
+    mDataCorruptedDueToQueueOverflow |= reason == DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW;
+}
+
 bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) {
     bool isActive = mEventActivationMap.empty();
     for (auto& it : mEventActivationMap) {
@@ -385,6 +391,69 @@
                             mShardCount);
 }
 
+sp<ConfigMetadataProvider> MetricProducer::getConfigMetadataProvider() const {
+    sp<ConfigMetadataProvider> provider = mConfigMetadataProvider.promote();
+    if (provider == nullptr) {
+        ALOGE("Could not promote ConfigMetadataProvider");
+        StatsdStats::getInstance().noteConfigMetadataProviderPromotionFailed(mConfigKey);
+    }
+    return provider;
+}
+
+size_t MetricProducer::computeBucketSizeLocked(const bool isFullBucket,
+                                               const MetricDimensionKey& dimKey,
+                                               const bool isFirstBucket) const {
+    size_t bucketSize = 0;
+
+    // Bucket timestamps or bucket number
+    bucketSize += isFullBucket ? sizeof(int32_t) : 2 * sizeof(int64_t);
+
+    // Each dimension / state key can have multiple buckets. Add the size only for the first bucket.
+    if (isFirstBucket) {
+        bucketSize += dimKey.getSize(mShouldUseNestedDimensions);
+    }
+
+    return bucketSize;
+}
+
+size_t MetricProducer::computeOverheadSizeLocked(const bool hasPastBuckets,
+                                                 const bool dimensionGuardrailHit) const {
+    size_t overheadSize = 0;
+
+    // MetricId + isActive
+    overheadSize += sizeof(int64_t) + sizeof(bool);
+
+    if (hasPastBuckets) {
+        if (dimensionGuardrailHit) {
+            overheadSize += sizeof(int32_t);
+        }
+
+        // estimated_memory_bytes
+        overheadSize += sizeof(int32_t);
+        // mTimeBase and mBucketSizeNs
+        overheadSize += 2 * sizeof(int64_t);
+
+        if (!mShouldUseNestedDimensions) {
+            // Assume dimensions data adds an additional atomTag + # of dimension fields
+            overheadSize += sizeof(int32_t);
+            overheadSize += sizeof(int32_t) * mDimensionsInWhat.size();
+        }
+    }
+    return overheadSize;
+}
+
+size_t MetricProducer::computeSkippedBucketSizeLocked(const SkippedBucket& skippedBucket) const {
+    size_t skippedBucketSize = 0;
+
+    // Bucket Start, Bucket End
+    skippedBucketSize += 2 * sizeof(int64_t);
+
+    // DropType, Drop Time
+    skippedBucketSize += (sizeof(int32_t) + sizeof(int64_t)) * skippedBucket.dropEvents.size();
+
+    return skippedBucketSize;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h
index 133aa32..cb97ae5 100644
--- a/statsd/src/metrics/MetricProducer.h
+++ b/statsd/src/metrics/MetricProducer.h
@@ -18,6 +18,7 @@
 #define METRIC_PRODUCER_H
 
 #include <src/active_config_list.pb.h>
+#include <src/guardrail/stats_log_enums.pb.h>
 #include <utils/RefBase.h>
 
 #include <unordered_map>
@@ -27,6 +28,7 @@
 #include "condition/ConditionTimer.h"
 #include "condition/ConditionWizard.h"
 #include "config/ConfigKey.h"
+#include "config/ConfigMetadataProvider.h"
 #include "guardrail/StatsdStats.h"
 #include "matchers/EventMatcherWizard.h"
 #include "matchers/matcher_util.h"
@@ -36,6 +38,7 @@
 #include "state/StateManager.h"
 #include "utils/DbUtils.h"
 #include "utils/ShardOffsetProvider.h"
+#include "utils/api_tracing.h"
 
 namespace android {
 namespace os {
@@ -136,7 +139,8 @@
                            eventDeactivationMap,
                    const vector<int>& slicedStateAtoms,
                    const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap,
-                   const optional<bool> splitBucketForAppUpgrade);
+                   const optional<bool> splitBucketForAppUpgrade,
+                   const wp<ConfigMetadataProvider> configMetadataProvider);
 
     virtual ~MetricProducer(){};
 
@@ -195,15 +199,22 @@
      * Force a partial bucket split on boot complete.
      */
     virtual void onStatsdInitCompleted(int64_t eventTimeNs) {
+        ATRACE_CALL();
         std::lock_guard<std::mutex> lock(mMutex);
         flushLocked(eventTimeNs);
     }
+
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
         std::lock_guard<std::mutex> lock(mMutex);
         onMatchedLogEventLocked(matcherIndex, event);
     }
 
+    void onMatchedLogEventLost(int32_t atomId, DataCorruptedReason reason) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        onMatchedLogEventLostLocked(atomId, reason);
+    }
+
     void onConditionChanged(const bool condition, int64_t eventTime) {
         std::lock_guard<std::mutex> lock(mMutex);
         onConditionChangedLocked(condition, eventTime);
@@ -232,8 +243,8 @@
                       std::set<string> *str_set,
                       android::util::ProtoOutputStream* protoOutput) {
         std::lock_guard<std::mutex> lock(mMutex);
-        return onDumpReportLocked(dumpTimeNs, include_current_partial_bucket, erase_data,
-                dumpLatency, str_set, protoOutput);
+        onDumpReportLocked(dumpTimeNs, include_current_partial_bucket, erase_data, dumpLatency,
+                           str_set, protoOutput);
     }
 
     virtual optional<InvalidConfigReason> onConfigUpdatedLocked(
@@ -254,7 +265,7 @@
 
     void clearPastBuckets(const int64_t dumpTimeNs) {
         std::lock_guard<std::mutex> lock(mMutex);
-        return clearPastBucketsLocked(dumpTimeNs);
+        clearPastBucketsLocked(dumpTimeNs);
     }
 
     void prepareFirstBucket() {
@@ -428,14 +439,14 @@
 
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
+    virtual void onMatchedLogEventLostLocked(int32_t atomId, DataCorruptedReason reason);
     virtual void onConditionChangedLocked(const bool condition, int64_t eventTime) = 0;
     virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
                                                   const int64_t eventTime) = 0;
     virtual void onDumpReportLocked(const int64_t dumpTimeNs,
                                     const bool include_current_partial_bucket,
-                                    const bool erase_data,
-                                    const DumpLatency dumpLatency,
-                                    std::set<string> *str_set,
+                                    const bool erase_data, const DumpLatency dumpLatency,
+                                    std::set<string>* str_set,
                                     android::util::ProtoOutputStream* protoOutput) = 0;
     virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
     virtual void prepareFirstBucketLocked(){};
@@ -446,6 +457,15 @@
     void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
     void cancelEventActivationLocked(int deactivationTrackerIndex);
 
+    // Computes the size of a newly added bucket to this metric, taking into account any new
+    // dimensions that are introduced if necessary.
+    virtual size_t computeBucketSizeLocked(const bool isFullBucket,
+                                           const MetricDimensionKey& dimKey,
+                                           const bool isFirstBucket) const;
+    size_t computeOverheadSizeLocked(const bool hasPastBuckets,
+                                     const bool dimensionGuardrailHit) const;
+    size_t computeSkippedBucketSizeLocked(const SkippedBucket& skippedBucket) const;
+
     bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
 
     virtual void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) {
@@ -577,6 +597,14 @@
 
     int mShardCount;
 
+    sp<ConfigMetadataProvider> getConfigMetadataProvider() const;
+
+    wp<ConfigMetadataProvider> mConfigMetadataProvider;
+    bool mDataCorruptedDueToSocketLoss = false;
+    bool mDataCorruptedDueToQueueOverflow = false;
+
+    size_t mTotalDataSize = 0;
+
     FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
     FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
     FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
@@ -614,6 +642,9 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
 
+    FRIEND_TEST(MetricsManagerTest, TestOnLogEventLossForAllowedFromAnyUidAtom);
+    FRIEND_TEST(MetricsManagerTest, TestOnLogEventLossForNotAllowedAtom);
+
     FRIEND_TEST(MetricsManagerUtilTest, TestInitialConditions);
     FRIEND_TEST(MetricsManagerUtilTest, TestSampledMetrics);
 
@@ -624,6 +655,10 @@
     FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricsMultipleTypes);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts);
+
+    FRIEND_TEST(EventMetricProducerTest, TestCorruptedDataReasonSocketLoss_OnDumpReport);
+    FRIEND_TEST(EventMetricProducerTest, TestCorruptedDataReasonSocketLoss_OnDropData);
+    FRIEND_TEST(EventMetricProducerTest, TestCorruptedDataReasonSocketLoss_OnClearPastBuckets);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/MetricsManager.cpp b/statsd/src/metrics/MetricsManager.cpp
index 6ce8bce..1ab1e38 100644
--- a/statsd/src/metrics/MetricsManager.cpp
+++ b/statsd/src/metrics/MetricsManager.cpp
@@ -34,6 +34,7 @@
 #include "stats_util.h"
 #include "statslog_statsd.h"
 #include "utils/DbUtils.h"
+#include "utils/api_tracing.h"
 
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_INT32;
@@ -78,7 +79,8 @@
       mPullerManager(pullerManager),
       mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
                           config.whitelisted_atom_ids().end()),
-      mShouldPersistHistory(config.persist_locally()) {
+      mShouldPersistHistory(config.persist_locally()),
+      mUseV2SoftMemoryCalculation(config.statsd_config_options().use_v2_soft_memory_limit()) {
     if (!isAtLeastU() && config.has_restricted_metrics_delegate_package_name()) {
         mInvalidConfigReason =
                 InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
@@ -91,7 +93,7 @@
     refreshTtl(timeBaseNs);
     mInvalidConfigReason = initStatsdConfig(
             key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseNs, currentTimeNs, mTagIdsToMatchersMap, mAllAtomMatchingTrackers,
+            timeBaseNs, currentTimeNs, this, mTagIdsToMatchersMap, mAllAtomMatchingTrackers,
             mAtomMatchingTrackerMap, mAllConditionTrackers, mConditionTrackerMap,
             mAllMetricProducers, mMetricProducerMap, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
             mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
@@ -162,7 +164,7 @@
             mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
             timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
             mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap,
-            mAllAnomalyTrackers, mAlertTrackerMap, mStateProtoHashes, mTagIdsToMatchersMap,
+            mAllAnomalyTrackers, mAlertTrackerMap, mStateProtoHashes, this, mTagIdsToMatchersMap,
             newAtomMatchingTrackers, newAtomMatchingTrackerMap, newConditionTrackers,
             newConditionTrackerMap, newMetricProducers, newMetricProducerMap, newAnomalyTrackers,
             newAlertTrackerMap, newPeriodicAlarmTrackers, mConditionToMetricMap,
@@ -191,6 +193,7 @@
                                config.whitelisted_atom_ids().end());
     mShouldPersistHistory = config.persist_locally();
     mPackageCertificateHashSizeBytes = config.package_certificate_hash_size_bytes();
+    mUseV2SoftMemoryCalculation = config.statsd_config_options().use_v2_soft_memory_limit();
 
     // Store the sub-configs used.
     mAnnotations.clear();
@@ -419,6 +422,7 @@
 }
 
 void MetricsManager::onStatsdInitCompleted(const int64_t eventTimeNs) {
+    ATRACE_CALL();
     // Inform all metric producers.
     for (const auto& it : mAllMetricProducers) {
         it->onStatsdInitCompleted(eventTimeNs);
@@ -442,6 +446,10 @@
     return uids;
 }
 
+bool MetricsManager::useV2SoftMemoryCalculation() {
+    return mUseV2SoftMemoryCalculation;
+}
+
 void MetricsManager::dumpStates(int out, bool verbose) {
     dprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str());
     {
@@ -507,20 +515,19 @@
     VLOG("=========================Metric Reports End==========================");
 }
 
-bool MetricsManager::checkLogCredentials(const LogEvent& event) {
-    if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) {
+bool MetricsManager::checkLogCredentials(const int32_t uid, const int32_t atomId) const {
+    if (mWhitelistedAtomIds.find(atomId) != mWhitelistedAtomIds.end()) {
         return true;
     }
 
-    if (event.GetUid() == AID_ROOT ||
-        (event.GetUid() >= AID_SYSTEM && event.GetUid() < AID_SHELL)) {
+    if (uid == AID_ROOT || (uid >= AID_SYSTEM && uid < AID_SHELL)) {
         // enable atoms logged from pre-installed Android system services
         return true;
     }
 
     std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
-    if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) {
-        VLOG("log source %d not on the whitelist", event.GetUid());
+    if (mAllowedLogSources.find(uid) == mAllowedLogSources.end()) {
+        VLOG("log source %d not on the whitelist", uid);
         return false;
     }
     return true;
@@ -532,11 +539,24 @@
         return;
     }
 
+    const int tagId = event.GetTagId();
+
+    if (tagId == util::STATS_SOCKET_LOSS_REPORTED) {
+        // Hard coded logic to handle socket loss info to highlight metric corruption reason
+        // STATS_SOCKET_LOSS_REPORTED might not be part of atoms allow list - but some of lost
+        // atoms can be always allowed - that is the reason to evaluate SocketLossInfo content prior
+        // the checkLogCredentials below
+        const std::optional<SocketLossInfo>& lossInfo = toSocketLossInfo(event);
+        if (lossInfo) {
+            onLogEventLost(*lossInfo);
+        }
+        // next, atom is going to be propagated to be consumed by metrics if any
+    }
+
     if (!checkLogCredentials(event)) {
         return;
     }
 
-    const int tagId = event.GetTagId();
     const int64_t eventTimeNs = event.GetElapsedTimestampNs();
 
     bool isActive = mIsAlwaysActive;
@@ -695,6 +715,48 @@
     }
 }
 
+void MetricsManager::onLogEventLost(const SocketLossInfo& socketLossInfo) {
+    // socketLossInfo stores atomId per UID - to eliminate duplicates using set
+    const set<int> uniqueLostAtomIds(socketLossInfo.atomIds.begin(), socketLossInfo.atomIds.end());
+
+    // pass lost atom id to all relevant metrics
+    for (const auto lostAtomId : uniqueLostAtomIds) {
+        /**
+         * Socket loss atom:
+         *  - comes from a specific uid (originUid)
+         *  - specifies the uid in the atom payload (socketLossInfo.uid)
+         *  - provides a list of atom ids that are lost
+         *
+         * For atom id that is lost (lostAtomId below):
+         * - if that atom id is allowed from any uid, then always count this atom as lost
+         * - else, if the originUid (from ucred) (socketLossInfo.uid below - is the same for all
+         *   uniqueLostAtomIds) is in the allowed log sources - count this atom as lost
+         */
+
+        if (!checkLogCredentials(socketLossInfo.uid, lostAtomId)) {
+            continue;
+        }
+
+        const auto matchersIt = mTagIdsToMatchersMap.find(lostAtomId);
+        if (matchersIt == mTagIdsToMatchersMap.end()) {
+            // atom is lost - but no metrics in config reference it
+            continue;
+        }
+        const auto& matchersIndexesListForLostAtom = matchersIt->second;
+        for (const auto matcherIndex : matchersIndexesListForLostAtom) {
+            auto it = mTrackerToMetricMap.find(matcherIndex);
+            if (it == mTrackerToMetricMap.end()) {
+                continue;
+            }
+            auto& metricsList = it->second;
+            for (const int metricIndex : metricsList) {
+                mAllMetricProducers[metricIndex]->onMatchedLogEventLost(lostAtomId,
+                                                                        DATA_CORRUPTED_SOCKET_LOSS);
+            }
+        }
+    }
+}
+
 void MetricsManager::onAnomalyAlarmFired(
         const int64_t timestampNs,
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
diff --git a/statsd/src/metrics/MetricsManager.h b/statsd/src/metrics/MetricsManager.h
index 86cacf3..22d5ecf 100644
--- a/statsd/src/metrics/MetricsManager.h
+++ b/statsd/src/metrics/MetricsManager.h
@@ -23,6 +23,7 @@
 #include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionTracker.h"
 #include "config/ConfigKey.h"
+#include "config/ConfigMetadataProvider.h"
 #include "external/StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
@@ -37,7 +38,9 @@
 namespace statsd {
 
 // A MetricsManager is responsible for managing metrics from one single config source.
-class MetricsManager : public virtual RefBase, public virtual PullUidProvider {
+class MetricsManager : public virtual RefBase,
+                       public virtual PullUidProvider,
+                       public virtual ConfigMetadataProvider {
 public:
     MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, int64_t timeBaseNs,
                    const int64_t currentTimeNs, const sp<UidMap>& uidMap,
@@ -54,8 +57,6 @@
     // Return whether the configuration is valid.
     bool isConfigValid() const;
 
-    bool checkLogCredentials(const LogEvent& event);
-
     virtual void onLogEvent(const LogEvent& event);
 
     void onAnomalyAlarmFired(
@@ -78,6 +79,8 @@
 
     vector<int32_t> getPullAtomUids(int32_t atomId) override;
 
+    bool useV2SoftMemoryCalculation() override;
+
     bool shouldWriteToDisk() const {
         return mNoReportMetricIds.size() != mAllMetricProducers.size();
     }
@@ -247,6 +250,7 @@
     std::list<std::pair<const int64_t, const int32_t>> mAnnotations;
 
     bool mShouldPersistHistory;
+    bool mUseV2SoftMemoryCalculation;
 
     // All event tags that are interesting to config metrics matchers.
     std::unordered_map<int, std::vector<int>> mTagIdsToMatchersMap;
@@ -319,6 +323,12 @@
 
     std::vector<int> mMetricIndexesWithActivation;
 
+    inline bool checkLogCredentials(const LogEvent& event) const {
+        return checkLogCredentials(event.GetUid(), event.GetTagId());
+    }
+
+    bool checkLogCredentials(int32_t uid, int32_t atomId) const;
+
     void initAllowedLogSources();
 
     void initPullAtomSources();
@@ -359,6 +369,8 @@
     // metrics.
     void setTriggerGetDataBytesFromConfig(const StatsdConfig& config);
 
+    void onLogEventLost(const SocketLossInfo& socketLossInfo);
+
     // The memory limit in bytes for storing metrics
     size_t mMaxMetricsBytes;
 
@@ -368,6 +380,8 @@
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
+
+    FRIEND_TEST(GaugeMetricE2ePushedTest, TestDimensionalSampling);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation);
     FRIEND_TEST(GaugeMetricE2ePushedTest, TestMultipleFieldsForPushedEvent);
@@ -398,7 +412,10 @@
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 
     FRIEND_TEST(MetricsManagerTest, TestLogSources);
+    FRIEND_TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom);
     FRIEND_TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate);
+    FRIEND_TEST(MetricsManagerTest, TestOnLogEventLossForAllowedFromAnyUidAtom);
+    FRIEND_TEST(MetricsManagerTest, TestOnLogEventLossForNotAllowedAtom);
     FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig);
     FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfigUpdate);
     FRIEND_TEST(MetricsManagerUtilTest, TestSampledMetrics);
@@ -435,7 +452,8 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
-    FRIEND_TEST(GaugeMetricE2ePushedTest, TestDimensionalSampling);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithMultipleAggTypes);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithDefaultAggType);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/NumericValueMetricProducer.cpp b/statsd/src/metrics/NumericValueMetricProducer.cpp
index 4024cce..ed5032b 100644
--- a/statsd/src/metrics/NumericValueMetricProducer.cpp
+++ b/statsd/src/metrics/NumericValueMetricProducer.cpp
@@ -22,6 +22,7 @@
 #include <limits.h>
 #include <stdlib.h>
 
+#include "FieldValue.h"
 #include "guardrail/StatsdStats.h"
 #include "metrics/parsing_utils/metrics_manager_util.h"
 #include "stats_log_util.h"
@@ -66,11 +67,13 @@
         const PullOptions& pullOptions, const BucketOptions& bucketOptions,
         const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
         const StateOptions& stateOptions, const ActivationOptions& activationOptions,
-        const GuardrailOptions& guardrailOptions)
+        const GuardrailOptions& guardrailOptions,
+        const wp<ConfigMetadataProvider> configMetadataProvider)
     : ValueMetricProducer(metric.id(), key, protoHash, pullOptions, bucketOptions, whatOptions,
-                          conditionOptions, stateOptions, activationOptions, guardrailOptions),
+                          conditionOptions, stateOptions, activationOptions, guardrailOptions,
+                          configMetadataProvider),
       mUseAbsoluteValueOnReset(metric.use_absolute_value_on_reset()),
-      mAggregationType(metric.aggregation_type()),
+      mAggregationTypes(whatOptions.aggregationTypes),
       mIncludeSampleSize(metric.has_include_sample_size()
                                  ? metric.include_sample_size()
                                  : metric.aggregation_type() == ValueMetric_AggregationType_AVG),
@@ -80,7 +83,8 @@
       mUseZeroDefaultBase(metric.use_zero_default_base()),
       mHasGlobalBase(false),
       mMaxPullDelayNs(metric.has_max_pull_delay_sec() ? metric.max_pull_delay_sec() * NS_PER_SEC
-                                                      : StatsdStats::kPullMaxDelayNs) {
+                                                      : StatsdStats::kPullMaxDelayNs),
+      mDedupedFieldMatchers(dedupFieldMatchers(whatOptions.fieldMatchers)) {
     // TODO(b/186677791): Use initializer list to initialize mUploadThreshold.
     if (metric.has_threshold()) {
         mUploadThreshold = metric.threshold();
@@ -274,9 +278,9 @@
 
             // Get dimensions_in_what key and value indices.
             HashableDimensionKey dimensionsInWhat;
-            vector<int> valueIndices(mFieldMatchers.size(), -1);
+            vector<int> valueIndices(mDedupedFieldMatchers.size(), -1);
             const LogEvent& eventRef = transformedEvent == nullptr ? *data : *transformedEvent;
-            if (!filterValues(mDimensionsInWhat, mFieldMatchers, eventRef.getValues(),
+            if (!filterValues(mDimensionsInWhat, mDedupedFieldMatchers, eventRef.getValues(),
                               dimensionsInWhat, valueIndices)) {
                 StatsdStats::getInstance().noteBadValueType(mMetricId);
             }
@@ -478,7 +482,7 @@
         }
 
         if (interval.hasValue()) {
-            switch (mAggregationType) {
+            switch (getAggregationTypeLocked(i)) {
                 case ValueMetric::SUM:
                     // for AVG, we add up and take average when flushing the bucket
                 case ValueMetric::AVG:
@@ -626,7 +630,30 @@
     }
 }
 
+// Estimate for the size of NumericValues.
+size_t NumericValueMetricProducer::getAggregatedValueSize(const Value& value) const {
+    size_t valueSize = 0;
+    // Index
+    valueSize += sizeof(int32_t);
+
+    // Value
+    valueSize += value.getSize();
+
+    // Sample Size
+    if (mIncludeSampleSize) {
+        valueSize += sizeof(int32_t);
+    }
+    return valueSize;
+}
+
 size_t NumericValueMetricProducer::byteSizeLocked() const {
+    sp<ConfigMetadataProvider> configMetadataProvider = getConfigMetadataProvider();
+    if (configMetadataProvider != nullptr && configMetadataProvider->useV2SoftMemoryCalculation()) {
+        bool dimensionGuardrailHit = StatsdStats::getInstance().hasHitDimensionGuardrail(mMetricId);
+        return computeOverheadSizeLocked(!mPastBuckets.empty() || !mSkippedBuckets.empty(),
+                                         dimensionGuardrailHit) +
+               mTotalDataSize;
+    }
     size_t totalSize = 0;
     for (const auto& [_, buckets] : mPastBuckets) {
         totalSize += buckets.size() * kBucketSize;
@@ -664,7 +691,7 @@
 }
 
 Value NumericValueMetricProducer::getFinalValue(const Interval& interval) const {
-    if (mAggregationType != ValueMetric::AVG) {
+    if (getAggregationTypeLocked(interval.aggIndex) != ValueMetric::AVG) {
         return interval.aggregate;
     } else {
         double sum = interval.aggregate.type == LONG ? (double)interval.aggregate.long_value
diff --git a/statsd/src/metrics/NumericValueMetricProducer.h b/statsd/src/metrics/NumericValueMetricProducer.h
index 775fc1a..8309aef 100644
--- a/statsd/src/metrics/NumericValueMetricProducer.h
+++ b/statsd/src/metrics/NumericValueMetricProducer.h
@@ -36,7 +36,8 @@
                                const ConditionOptions& conditionOptions,
                                const StateOptions& stateOptions,
                                const ActivationOptions& activationOptions,
-                               const GuardrailOptions& guardrailOptions);
+                               const GuardrailOptions& guardrailOptions,
+                               const wp<ConfigMetadataProvider> configMetadataProvider);
 
     // Process data pulled on bucket boundary.
     void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData, PullResult pullResult,
@@ -142,12 +143,18 @@
     // Internal function to calculate the current used bytes.
     size_t byteSizeLocked() const override;
 
-    void combineValueFields(pair<LogEvent, vector<int>>& eventValues, const LogEvent& newEvent,
-                            const vector<int>& newValueIndices) const;
+    void combineValueFields(pair<LogEvent, std::vector<int>>& eventValues, const LogEvent& newEvent,
+                            const std::vector<int>& newValueIndices) const;
+
+    ValueMetric::AggregationType getAggregationTypeLocked(int index) const {
+        return mAggregationTypes.size() == 1 ? mAggregationTypes[0] : mAggregationTypes[index];
+    }
+
+    size_t getAggregatedValueSize(const Value& value) const override;
 
     const bool mUseAbsoluteValueOnReset;
 
-    const ValueMetric::AggregationType mAggregationType;
+    const std::vector<ValueMetric::AggregationType> mAggregationTypes;
 
     const bool mIncludeSampleSize;
 
@@ -171,6 +178,9 @@
 
     const int64_t mMaxPullDelayNs;
 
+    // Deduped value fields for matching.
+    const std::vector<Matcher> mDedupedFieldMatchers;
+
     // For anomaly detection.
     std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket;
 
@@ -232,6 +242,8 @@
     FRIEND_TEST(NumericValueMetricProducerTest,
                 TestSlicedStateWithMultipleDimensionsMissingDataInPull);
     FRIEND_TEST(NumericValueMetricProducerTest, TestUploadThreshold);
+    FRIEND_TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPulled);
+    FRIEND_TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPushed);
 
     FRIEND_TEST(NumericValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed);
     FRIEND_TEST(NumericValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed);
diff --git a/statsd/src/metrics/RestrictedEventMetricProducer.cpp b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
index f38e835..77682dd 100644
--- a/statsd/src/metrics/RestrictedEventMetricProducer.cpp
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
@@ -35,13 +35,14 @@
         const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
         const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
         const uint64_t protoHash, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, protoHash,
-                          startTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-                          stateGroupMap),
+                          startTimeNs, configMetadataProvider, eventActivationMap,
+                          eventDeactivationMap, slicedStateAtoms, stateGroupMap),
       mRestrictedDataCategory(CATEGORY_UNKNOWN) {
 }
 
@@ -57,11 +58,11 @@
         StatsdStats::getInstance().noteRestrictedMetricCategoryChanged(mConfigKey, mMetricId);
         deleteMetricTable();
         mLogEvents.clear();
-        mTotalSize = 0;
+        mTotalDataSize = 0;
     }
     mRestrictedDataCategory = event.getRestrictionCategory();
     mLogEvents.push_back(event);
-    mTotalSize += getSize(event.getValues()) + sizeof(event);
+    mTotalDataSize += getSize(event.getValues()) + sizeof(event);
 }
 
 void RestrictedEventMetricProducer::onDumpReportLocked(
@@ -93,7 +94,7 @@
 
 void RestrictedEventMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
     mLogEvents.clear();
-    mTotalSize = 0;
+    mTotalDataSize = 0;
     StatsdStats::getInstance().noteBucketDropped(mMetricId);
 }
 
@@ -129,7 +130,7 @@
                 mConfigKey, mMetricId, getElapsedRealtimeNs() - flushStartNs);
     }
     mLogEvents.clear();
-    mTotalSize = 0;
+    mTotalDataSize = 0;
 }
 
 bool RestrictedEventMetricProducer::writeMetricMetadataToProto(
diff --git a/statsd/src/metrics/RestrictedEventMetricProducer.h b/statsd/src/metrics/RestrictedEventMetricProducer.h
index 1f68387..7f9a5eb 100644
--- a/statsd/src/metrics/RestrictedEventMetricProducer.h
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.h
@@ -16,6 +16,7 @@
             const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
             const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
             const uint64_t protoHash, int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
             const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                     eventDeactivationMap = {},
diff --git a/statsd/src/metrics/ValueMetricProducer.cpp b/statsd/src/metrics/ValueMetricProducer.cpp
index 732cccc..ff65792 100644
--- a/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/statsd/src/metrics/ValueMetricProducer.cpp
@@ -54,6 +54,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 // for *MetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
@@ -76,12 +77,14 @@
         const PullOptions& pullOptions, const BucketOptions& bucketOptions,
         const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
         const StateOptions& stateOptions, const ActivationOptions& activationOptions,
-        const GuardrailOptions& guardrailOptions)
+        const GuardrailOptions& guardrailOptions,
+        const wp<ConfigMetadataProvider> configMetadataProvider)
     : MetricProducer(metricId, key, bucketOptions.timeBaseNs, conditionOptions.conditionIndex,
                      conditionOptions.initialConditionCache, conditionOptions.conditionWizard,
                      protoHash, activationOptions.eventActivationMap,
                      activationOptions.eventDeactivationMap, stateOptions.slicedStateAtoms,
-                     stateOptions.stateGroupMap, bucketOptions.splitBucketForAppUpgrade),
+                     stateOptions.stateGroupMap, bucketOptions.splitBucketForAppUpgrade,
+                     configMetadataProvider),
       mWhatMatcherIndex(whatOptions.whatMatcherIndex),
       mEventMatcherWizard(whatOptions.matcherWizard),
       mPullerManager(pullOptions.pullerManager),
@@ -155,6 +158,7 @@
 template <typename AggregatedValue, typename DimExtras>
 void ValueMetricProducer<AggregatedValue, DimExtras>::onStatsdInitCompleted(
         const int64_t eventTimeNs) {
+    ATRACE_CALL();
     lock_guard<mutex> lock(mMutex);
 
     if (isPulled() && mCondition == ConditionState::kTrue && mIsActive) {
@@ -222,10 +226,35 @@
 }
 
 template <typename AggregatedValue, typename DimExtras>
+size_t ValueMetricProducer<AggregatedValue, DimExtras>::computeValueBucketSizeLocked(
+        const bool isFullBucket, const MetricDimensionKey& dimKey, const bool isFirstBucket,
+        const PastBucket<AggregatedValue>& bucket) const {
+    size_t bucketSize =
+            MetricProducer::computeBucketSizeLocked(isFullBucket, dimKey, isFirstBucket);
+
+    for (const auto& value : bucket.aggregates) {
+        bucketSize += getAggregatedValueSize(value);
+    }
+
+    // ConditionTrueNanos
+    if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) {
+        bucketSize += sizeof(int64_t);
+    }
+
+    // ConditionCorrectionNanos
+    if (getDumpProtoFields().conditionCorrectionNsFieldId.has_value() && isPulled() &&
+        mConditionCorrectionThresholdNs &&
+        (abs(bucket.mConditionCorrectionNs) >= mConditionCorrectionThresholdNs)) {
+        bucketSize += sizeof(int64_t);
+    }
+    return bucketSize;
+}
+
+template <typename AggregatedValue, typename DimExtras>
 void ValueMetricProducer<AggregatedValue, DimExtras>::onStateChanged(
         int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
         const FieldValue& oldState, const FieldValue& newState) {
-    // TODO(b/189353769): Acquire lock.
+    std::lock_guard<std::mutex> lock(mMutex);
     VLOG("ValueMetricProducer %lld onStateChanged time %lld, State %d, key %s, %d -> %d",
          (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
          oldState.mValue.int_value, newState.mValue.int_value);
@@ -287,6 +316,7 @@
         const int64_t dumpTimeNs) {
     mPastBuckets.clear();
     mSkippedBuckets.clear();
+    mTotalDataSize = 0;
 }
 
 template <typename AggregatedValue, typename DimExtras>
@@ -324,6 +354,9 @@
         return;
     }
 
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
+
     if (StatsdStats::getInstance().hasHitDimensionGuardrail(mMetricId)) {
         protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_DIMENSION_GUARDRAIL_HIT, true);
     }
@@ -441,6 +474,7 @@
     if (eraseData) {
         mPastBuckets.clear();
         mSkippedBuckets.clear();
+        mTotalDataSize = 0;
     }
 }
 
@@ -816,6 +850,9 @@
             }
 
             auto& bucketList = mPastBuckets[metricDimensionKey];
+            const bool isFirstBucket = bucketList.empty();
+            mTotalDataSize += computeValueBucketSizeLocked(
+                    eventTimeNs >= fullBucketEndTimeNs, metricDimensionKey, isFirstBucket, bucket);
             bucketList.push_back(std::move(bucket));
         }
         if (!bucketHasData) {
@@ -827,6 +864,7 @@
         mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
         mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTimeNs;
         mSkippedBuckets.push_back(mCurrentSkippedBucket);
+        mTotalDataSize += computeSkippedBucketSizeLocked(mCurrentSkippedBucket);
     }
 
     // This means that the current bucket was not flushed before a forced bucket split.
diff --git a/statsd/src/metrics/ValueMetricProducer.h b/statsd/src/metrics/ValueMetricProducer.h
index 92541029..78761eb 100644
--- a/statsd/src/metrics/ValueMetricProducer.h
+++ b/statsd/src/metrics/ValueMetricProducer.h
@@ -88,6 +88,7 @@
         const sp<EventMatcherWizard>& matcherWizard;
         const FieldMatcher& dimensionsInWhat;
         const vector<Matcher>& fieldMatchers;
+        const vector<ValueMetric::AggregationType> aggregationTypes;
     };
 
     struct ConditionOptions {
@@ -138,7 +139,8 @@
                         const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
                         const StateOptions& stateOptions,
                         const ActivationOptions& activationOptions,
-                        const GuardrailOptions& guardrailOptions);
+                        const GuardrailOptions& guardrailOptions,
+                        const wp<ConfigMetadataProvider> configMetadataProvider);
 
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -230,6 +232,12 @@
             std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
             std::vector<int>& metricsWithActivation) override;
 
+    size_t computeValueBucketSizeLocked(const bool isFullBucket, const MetricDimensionKey& dimKey,
+                                        const bool isFirstBucket,
+                                        const PastBucket<AggregatedValue>& bucket) const;
+
+    virtual size_t getAggregatedValueSize(const AggregatedValue& value) const = 0;
+
     virtual optional<int64_t> getConditionIdForMetric(const StatsdConfig& config,
                                                       const int configIndex) const = 0;
 
diff --git a/statsd/src/metrics/duration_helper/DurationTracker.h b/statsd/src/metrics/duration_helper/DurationTracker.h
index f86197a..a6cbfff 100644
--- a/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -162,6 +162,8 @@
 protected:
     virtual bool hasStartedDuration() const = 0;
 
+    // Convenience to compute the current bucket's end time, which is always aligned with the
+    // start time of the metric.
     int64_t getCurrentBucketEndTimeNs() const {
         return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
     }
@@ -207,12 +209,6 @@
         }
     }
 
-    // Convenience to compute the current bucket's end time, which is always aligned with the
-    // start time of the metric.
-    int64_t getCurrentBucketEndTimeNs() {
-        return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
-    }
-
     void setEventKey(const MetricDimensionKey& eventKey) {
         mEventKey = eventKey;
     }
diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 18d7a32..a296dad 100644
--- a/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -730,6 +730,7 @@
         const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
         const set<int64_t>& replacedStates, const unordered_map<int64_t, int>& oldMetricProducerMap,
         const vector<sp<MetricProducer>>& oldMetricProducers,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         unordered_map<int64_t, int>& newMetricProducerMap,
         vector<sp<MetricProducer>>& newMetricProducers,
         unordered_map<int, vector<int>>& conditionToMetricMap,
@@ -802,7 +803,7 @@
                         allStateGroupMaps, metricToActivationMap, trackerToMetricMap,
                         conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -844,7 +845,7 @@
                         allStateGroupMaps, metricToActivationMap, trackerToMetricMap,
                         conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -885,7 +886,7 @@
                         initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
                         conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -928,7 +929,7 @@
                         stateAtomIdMap, allStateGroupMaps, metricToActivationMap,
                         trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -970,7 +971,7 @@
                         conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
                         metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                         activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                        metricsWithActivation, invalidConfigReason);
+                        metricsWithActivation, invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -1014,7 +1015,7 @@
                         stateAtomIdMap, allStateGroupMaps, metricToActivationMap,
                         trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -1207,6 +1208,7 @@
         const vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
         const unordered_map<int64_t, int>& oldAlertTrackerMap,
         const map<int64_t, uint64_t>& oldStateProtoHashes,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
         unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -1268,9 +1270,10 @@
             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
             newConditionTrackerMap, replacedConditions, newConditionTrackers, conditionCache,
             stateAtomIdMap, allStateGroupMaps, replacedStates, oldMetricProducerMap,
-            oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-            trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap,
-            deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics);
+            oldMetricProducers, configMetadataProvider, newMetricProducerMap, newMetricProducers,
+            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+            activationTrackerToMetricMap, deactivationTrackerToMetricMap, metricsWithActivation,
+            replacedMetrics);
     if (invalidConfigReason.has_value()) {
         ALOGE("updateMetrics failed");
         return invalidConfigReason;
diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.h b/statsd/src/metrics/parsing_utils/config_update_utils.h
index 8b6c784..a8915c2 100644
--- a/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -21,6 +21,7 @@
 #include "anomaly/AlarmMonitor.h"
 #include "anomaly/AlarmTracker.h"
 #include "condition/ConditionTracker.h"
+#include "config/ConfigMetadataProvider.h"
 #include "external/StatsPullerManager.h"
 #include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
@@ -179,6 +180,7 @@
         const std::set<int64_t>& replacedStates,
         const std::unordered_map<int64_t, int>& oldMetricProducerMap,
         const std::vector<sp<MetricProducer>>& oldMetricProducers,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int64_t, int>& newMetricProducerMap,
         std::vector<sp<MetricProducer>>& newMetricProducers,
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
@@ -245,6 +247,7 @@
         const std::vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
         const std::unordered_map<int64_t, int>& oldAlertTrackerMap,
         const std::map<int64_t, uint64_t>& oldStateProtoHashes,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
         std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 673ff45..43744d3 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -509,7 +509,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in CountMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -597,10 +598,10 @@
         return nullopt;
     }
 
-    sp<MetricProducer> metricProducer =
-            new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
-                                    metricHash, timeBaseNs, currentTimeNs, eventActivationMap,
-                                    eventDeactivationMap, slicedStateAtoms, stateGroupMap);
+    sp<MetricProducer> metricProducer = new CountMetricProducer(
+            key, metric, conditionIndex, initialConditionCache, wizard, metricHash, timeBaseNs,
+            currentTimeNs, configMetadataProvider, eventActivationMap, eventDeactivationMap,
+            slicedStateAtoms, stateGroupMap);
 
     SamplingInfo samplingInfo;
     if (metric.has_dimensional_sampling_info()) {
@@ -630,7 +631,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in DurationMetric \"%lld\"",
               (long long)metric.id());
@@ -778,8 +780,8 @@
     sp<MetricProducer> metricProducer = new DurationMetricProducer(
             key, metric, conditionIndex, initialConditionCache, whatIndex, startIndex, stopIndex,
             stopAllIndex, nesting, wizard, metricHash, internalDimensions, timeBaseNs,
-            currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-            stateGroupMap);
+            currentTimeNs, configMetadataProvider, eventActivationMap, eventDeactivationMap,
+            slicedStateAtoms, stateGroupMap);
     if (!metricProducer->isValid()) {
         // TODO: Remove once invalidConfigReason is added to the DurationMetricProducer constructor
         invalidConfigReason = InvalidConfigReason(
@@ -813,7 +815,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find the metric name or what in config");
         invalidConfigReason =
@@ -869,11 +872,11 @@
     if (config.has_restricted_metrics_delegate_package_name()) {
         return {new RestrictedEventMetricProducer(
                 key, metric, conditionIndex, initialConditionCache, wizard, metricHash, timeBaseNs,
-                eventActivationMap, eventDeactivationMap)};
+                configMetadataProvider, eventActivationMap, eventDeactivationMap)};
     }
     return {new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
-                                    metricHash, timeBaseNs, eventActivationMap,
-                                    eventDeactivationMap)};
+                                    metricHash, timeBaseNs, configMetadataProvider,
+                                    eventActivationMap, eventDeactivationMap)};
 }
 
 optional<sp<MetricProducer>> createNumericValueMetricProducerAndUpdateMetadata(
@@ -893,7 +896,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -922,6 +926,27 @@
         return nullopt;
     }
 
+    std::vector<ValueMetric::AggregationType> aggregationTypes;
+    if (metric.aggregation_types_size() != 0) {
+        if (metric.has_aggregation_type()) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_VALUE_METRIC_DEFINES_SINGLE_AND_MULTIPLE_AGG_TYPES,
+                    metric.id());
+            return nullopt;
+        }
+        if (metric.aggregation_types_size() != (int)fieldMatchers.size()) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE,
+                    metric.id());
+            return nullopt;
+        }
+        for (int i = 0; i < metric.aggregation_types_size(); i++) {
+            aggregationTypes.push_back(metric.aggregation_types(i));
+        }
+    } else {  // aggregation_type() is set or default is used.
+        aggregationTypes.push_back(metric.aggregation_type());
+    }
+
     int trackerIndex;
     invalidConfigReason = handleMetricWithAtomMatchingTrackers(
             metric.what(), metric.id(), metricIndex,
@@ -1019,10 +1044,11 @@
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              conditionCorrectionThresholdNs, getAppUpgradeBucketSplit(metric)},
             {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex,
-             matcherWizard, metric.dimensions_in_what(), fieldMatchers},
+             matcherWizard, metric.dimensions_in_what(), fieldMatchers, aggregationTypes},
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
-            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
+            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit},
+            configMetadataProvider);
 
     SamplingInfo samplingInfo;
     if (metric.has_dimensional_sampling_info()) {
@@ -1054,7 +1080,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in KllMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -1171,11 +1198,17 @@
             key, metric, metricHash, {/*pullTagId=*/-1, pullerManager},
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              /*conditionCorrectionThresholdNs=*/nullopt, getAppUpgradeBucketSplit(metric)},
-            {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex,
-             matcherWizard, metric.dimensions_in_what(), fieldMatchers},
+            {containsAnyPositionInDimensionsInWhat,
+             shouldUseNestedDimensions,
+             trackerIndex,
+             matcherWizard,
+             metric.dimensions_in_what(),
+             fieldMatchers,
+             {}},
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
-            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
+            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit},
+            configMetadataProvider);
 
     SamplingInfo samplingInfo;
     if (metric.has_dimensional_sampling_info()) {
@@ -1205,7 +1238,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -1299,6 +1333,26 @@
         return nullopt;
     }
 
+    if (metric.pull_probability() < 1 || metric.pull_probability() > 100) {
+        invalidConfigReason = InvalidConfigReason(
+                INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY, metric.id());
+        return nullopt;
+    }
+
+    if (metric.pull_probability() != 100) {
+        if (pullTagId == -1) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_GAUGE_METRIC_PUSHED_WITH_PULL_PROBABILITY, metric.id());
+            return nullopt;
+        }
+        if (metric.sampling_type() == GaugeMetric::RANDOM_ONE_SAMPLE) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_GAUGE_METRIC_RANDOM_ONE_SAMPLE_WITH_PULL_PROBABILITY,
+                    metric.id());
+            return nullopt;
+        }
+    }
+
     unordered_map<int, shared_ptr<Activation>> eventActivationMap;
     unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
     invalidConfigReason = handleMetricActivation(
@@ -1324,8 +1378,8 @@
     sp<MetricProducer> metricProducer = new GaugeMetricProducer(
             key, metric, conditionIndex, initialConditionCache, wizard, metricHash, trackerIndex,
             matcherWizard, pullTagId, triggerAtomId, atomTagId, timeBaseNs, currentTimeNs,
-            pullerManager, eventActivationMap, eventDeactivationMap, dimensionSoftLimit,
-            dimensionHardLimit);
+            pullerManager, configMetadataProvider, eventActivationMap, eventDeactivationMap,
+            dimensionSoftLimit, dimensionHardLimit);
 
     SamplingInfo samplingInfo;
     std::vector<Matcher> dimensionsInWhat;
@@ -1533,7 +1587,8 @@
         std::set<int64_t>& noReportMetricIds,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation) {
+        vector<int>& metricsWithActivation,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
     sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
@@ -1574,7 +1629,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1593,7 +1648,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1610,7 +1665,8 @@
                 atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap,
                 initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
                 conditionToMetricMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, invalidConfigReason);
+                deactivationAtomTrackerToMetricMap, metricsWithActivation, invalidConfigReason,
+                configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1628,7 +1684,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1646,7 +1702,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1664,7 +1720,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
                 metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1752,7 +1808,7 @@
         const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
         const sp<StatsPullerManager>& pullerManager, const sp<AlarmMonitor>& anomalyAlarmMonitor,
         const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
-        const int64_t currentTimeNs,
+        const int64_t currentTimeNs, const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         unordered_map<int64_t, int>& atomMatchingTrackerMap,
@@ -1808,7 +1864,7 @@
             allConditionTrackers, initialConditionCache, allMetricProducers, conditionToMetricMap,
             trackerToMetricMap, metricProducerMap, noReportMetricIds,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-            metricsWithActivation);
+            metricsWithActivation, configMetadataProvider);
     if (invalidConfigReason.has_value()) {
         ALOGE("initMetricProducers failed");
         return invalidConfigReason;
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index 41e8dc7..8a73ff0 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -22,6 +22,7 @@
 
 #include "anomaly/AlarmTracker.h"
 #include "condition/ConditionTracker.h"
+#include "config/ConfigMetadataProvider.h"
 #include "external/StatsPullerManager.h"
 #include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
@@ -111,8 +112,8 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a DurationMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
@@ -131,8 +132,8 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates an EventMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
@@ -149,8 +150,8 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a NumericValueMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
@@ -171,8 +172,8 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
@@ -191,8 +192,8 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a KllMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
@@ -213,7 +214,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason);
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates an AnomalyTracker and adds it to the appropriate metric.
 // Returns an sp to the AnomalyTracker, or nullopt if there was an error.
@@ -353,7 +355,8 @@
         std::set<int64_t>& noReportMetricIds,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation);
+        std::vector<int>& metricsWithActivation,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Initialize alarms
 // Is called both on initialize new configs and config updates since alarms do not have any state.
@@ -368,7 +371,7 @@
         const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
         const sp<StatsPullerManager>& pullerManager, const sp<AlarmMonitor>& anomalyAlarmMonitor,
         const sp<AlarmMonitor>& periodicAlarmMonitor, int64_t timeBaseNs,
-        const int64_t currentTimeNs,
+        const int64_t currentTimeNs, const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
diff --git a/statsd/src/packages/UidMap.cpp b/statsd/src/packages/UidMap.cpp
index 3177225..79e8534 100644
--- a/statsd/src/packages/UidMap.cpp
+++ b/statsd/src/packages/UidMap.cpp
@@ -638,6 +638,7 @@
                                                              {"AID_SDK_SANDBOX", 1090},
                                                              {"AID_SECURITY_LOG_WRITER", 1091},
                                                              {"AID_PRNG_SEEDER", 1092},
+                                                             {"AID_UPROBESTATS", 1093},
                                                              {"AID_SHELL", 2000},
                                                              {"AID_CACHE", 2001},
                                                              {"AID_DIAG", 2002},
diff --git a/statsd/src/shell/ShellSubscriber.cpp b/statsd/src/shell/ShellSubscriber.cpp
index 90ade1c..56c3ccd 100644
--- a/statsd/src/shell/ShellSubscriber.cpp
+++ b/statsd/src/shell/ShellSubscriber.cpp
@@ -24,6 +24,7 @@
 
 #include "guardrail/StatsdStats.h"
 #include "stats_log_util.h"
+#include "utils/api_tracing.h"
 
 using aidl::android::os::IStatsSubscriptionCallback;
 
@@ -126,6 +127,7 @@
 }
 
 void ShellSubscriber::onLogEvent(const LogEvent& event) {
+    ATRACE_CALL();
     // Skip if event is skipped
     if (event.isParsedHeaderOnly()) {
         return;
diff --git a/statsd/src/socket/StatsSocketListener.cpp b/statsd/src/socket/StatsSocketListener.cpp
index dd1013d..c9f8b89 100644
--- a/statsd/src/socket/StatsSocketListener.cpp
+++ b/statsd/src/socket/StatsSocketListener.cpp
@@ -33,6 +33,7 @@
 #include "logd/logevent_util.h"
 #include "stats_log_util.h"
 #include "statslog_statsd.h"
+#include "utils/api_tracing.h"
 
 namespace android {
 namespace os {
@@ -46,6 +47,7 @@
 }
 
 bool StatsSocketListener::onDataAvailable(SocketClient* cli) {
+    ATRACE_CALL();
     static bool name_set;
     if (!name_set) {
         prctl(PR_SET_NAME, "statsd.writer");
@@ -61,39 +63,61 @@
             NULL, 0, &iov, 1, control, sizeof(control), 0,
     };
 
-    int socket = cli->getSocket();
-
-    // To clear the entire buffer is secure/safe, but this contributes to 1.68%
-    // overhead under logging load. We are safe because we check counts, but
-    // still need to clear null terminator
-    // memset(buffer, 0, sizeof(buffer));
-    ssize_t n = recvmsg(socket, &hdr, 0);
-    if (n <= (ssize_t)(sizeof(android_log_header_t))) {
-        return false;
-    }
-
-    buffer[n] = 0;
-
-    struct ucred* cred = NULL;
-
-    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
-    while (cmsg != NULL) {
-        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) {
-            cred = (struct ucred*)CMSG_DATA(cmsg);
-            break;
+    const int socket = cli->getSocket();
+    int i = 0;
+    ssize_t n = 0;
+    while (n = recvmsg(socket, &hdr, MSG_DONTWAIT), n > 0) {
+        // To clear the entire buffer is secure/safe, but this contributes to 1.68%
+        // overhead under logging load. We are safe because we check counts, but
+        // still need to clear null terminator.
+        // Note that the memset, if needed, should happen before each read in the while loop.
+        // memset(buffer, 0, sizeof(buffer));
+        if (n <= (ssize_t)(sizeof(android_log_header_t))) {
+            return false;
         }
-        cmsg = CMSG_NXTHDR(&hdr, cmsg);
+        buffer[n] = 0;
+        i++;
+
+        struct ucred* cred = NULL;
+
+        struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
+        while (cmsg != NULL) {
+            if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) {
+                cred = (struct ucred*)CMSG_DATA(cmsg);
+                break;
+            }
+            cmsg = CMSG_NXTHDR(&hdr, cmsg);
+        }
+
+        struct ucred fake_cred;
+        if (cred == NULL) {
+            cred = &fake_cred;
+            cred->pid = 0;
+            cred->uid = DEFAULT_OVERFLOWUID;
+        }
+
+        const uint32_t uid = cred->uid;
+        const uint32_t pid = cred->pid;
+
+        processSocketMessage(buffer, n, uid, pid, *mQueue, *mLogEventFilter);
     }
 
-    struct ucred fake_cred;
-    if (cred == NULL) {
-        cred = &fake_cred;
-        cred->pid = 0;
-        cred->uid = DEFAULT_OVERFLOWUID;
+    StatsdStats::getInstance().noteBatchSocketRead(i);
+    return true;
+}
+
+void StatsSocketListener::processSocketMessage(const char* buffer, const uint32_t len, uint32_t uid,
+                                               uint32_t pid, LogEventQueue& queue,
+                                               const LogEventFilter& filter) {
+    ATRACE_CALL();
+    static const uint32_t kStatsEventTag = 1937006964;
+
+    if (len <= (ssize_t)(sizeof(android_log_header_t)) + sizeof(uint32_t)) {
+        return;
     }
 
-    uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t);
-    n -= sizeof(android_log_header_t);
+    const uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t);
+    uint32_t bufferLen = len - sizeof(android_log_header_t);
 
     // When a log failed to write to statsd socket (e.g., due ot EBUSY), a special message would
     // be sent to statsd when the socket communication becomes available again.
@@ -102,8 +126,9 @@
     // Note that all normal stats logs are in the format of event_list, so there won't be confusion.
     //
     // TODO(b/80538532): In addition to log it in StatsdStats, we should properly reset the config.
-    if (n == sizeof(android_log_event_long_t)) {
-        android_log_event_long_t* long_event = reinterpret_cast<android_log_event_long_t*>(ptr);
+    if (bufferLen == sizeof(android_log_event_long_t)) {
+        const android_log_event_long_t* long_event =
+                reinterpret_cast<const android_log_event_long_t*>(ptr);
         if (long_event->payload.type == EVENT_TYPE_LONG) {
             int64_t composed_long = long_event->payload.data;
 
@@ -113,33 +138,35 @@
             int32_t last_atom_tag = (int32_t)((0xffffffff00000000 & (uint64_t)composed_long) >> 32);
 
             ALOGE("Found dropped events: %d error %d last atom tag %d from uid %d", dropped_count,
-                  long_event->header.tag, last_atom_tag, cred->uid);
+                  long_event->header.tag, last_atom_tag, uid);
             StatsdStats::getInstance().noteLogLost((int32_t)getWallClockSec(), dropped_count,
-                                                   long_event->header.tag, last_atom_tag, cred->uid,
-                                                   cred->pid);
-            return true;
+                                                   long_event->header.tag, last_atom_tag, uid, pid);
+            return;
         }
     }
 
+    // test that received valid StatsEvent buffer
+    const uint32_t statsEventTag = *reinterpret_cast<const uint32_t*>(ptr);
+    if (statsEventTag != kStatsEventTag) {
+        return;
+    }
+
     // move past the 4-byte StatsEventTag
     const uint8_t* msg = ptr + sizeof(uint32_t);
-    const uint32_t len = n - sizeof(uint32_t);
-    const uint32_t uid = cred->uid;
-    const uint32_t pid = cred->pid;
+    bufferLen -= sizeof(uint32_t);
 
-    processMessage(msg, len, uid, pid, mQueue, mLogEventFilter);
-
-    return true;
+    processStatsEventBuffer(msg, bufferLen, uid, pid, queue, filter);
 }
 
-void StatsSocketListener::processMessage(const uint8_t* msg, uint32_t len, uint32_t uid,
-                                         uint32_t pid, const std::shared_ptr<LogEventQueue>& queue,
-                                         const std::shared_ptr<LogEventFilter>& filter) {
+void StatsSocketListener::processStatsEventBuffer(const uint8_t* msg, const uint32_t len,
+                                                  uint32_t uid, uint32_t pid, LogEventQueue& queue,
+                                                  const LogEventFilter& filter) {
+    ATRACE_CALL();
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
 
-    if (filter->getFilteringEnabled()) {
+    if (filter.getFilteringEnabled()) {
         const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(msg, len);
-        if (filter->isAtomInUse(logEvent->GetTagId())) {
+        if (filter.isAtomInUse(logEvent->GetTagId())) {
             logEvent->parseBody(bodyInfo);
         }
     } else {
@@ -165,7 +192,7 @@
         }
     }
 
-    const auto [success, oldestTimestamp, queueSize] = queue->push(std::move(logEvent));
+    const auto [success, oldestTimestamp, queueSize] = queue.push(std::move(logEvent));
     if (success) {
         StatsdStats::getInstance().noteEventQueueSize(queueSize, atomTimestamp);
     } else {
diff --git a/statsd/src/socket/StatsSocketListener.h b/statsd/src/socket/StatsSocketListener.h
index fcff2c2..2142e9e 100644
--- a/statsd/src/socket/StatsSocketListener.h
+++ b/statsd/src/socket/StatsSocketListener.h
@@ -50,6 +50,21 @@
     static int getLogSocket();
 
     /**
+     * @brief Helper API to parse raw socket data buffer, make the LogEvent & submit it into the
+     * queue. Performs preliminary data validation.
+     * Created as a separate API to be easily tested without StatsSocketListener instance
+     *
+     * @param buffer buffer to parse
+     * @param len size of buffer in bytes
+     * @param uid arguments for LogEvent constructor
+     * @param pid arguments for LogEvent constructor
+     * @param queue queue to submit the event
+     * @param filter to be used for event evaluation
+     */
+    static void processSocketMessage(const char* buffer, uint32_t len, uint32_t uid, uint32_t pid,
+                                     LogEventQueue& queue, const LogEventFilter& filter);
+
+    /**
      * @brief Helper API to parse buffer, make the LogEvent & submit it into the queue
      * Created as a separate API to be easily tested without StatsSocketListener instance
      *
@@ -60,9 +75,9 @@
      * @param queue queue to submit the event
      * @param filter to be used for event evaluation
      */
-    static void processMessage(const uint8_t* msg, uint32_t len, uint32_t uid, uint32_t pid,
-                               const std::shared_ptr<LogEventQueue>& queue,
-                               const std::shared_ptr<LogEventFilter>& filter);
+    static void processStatsEventBuffer(const uint8_t* msg, uint32_t len, uint32_t uid,
+                                        uint32_t pid, LogEventQueue& queue,
+                                        const LogEventFilter& filter);
 
     /**
      * Who is going to get the events when they're read.
@@ -71,10 +86,11 @@
 
     std::shared_ptr<LogEventFilter> mLogEventFilter;
 
+    friend void fuzzSocket(const uint8_t* data, size_t size);
+
     friend class SocketParseMessageTest;
-    friend void generateAtomLogging(const std::shared_ptr<LogEventQueue>& queue,
-                                    const std::shared_ptr<LogEventFilter>& filter, int eventCount,
-                                    int startAtomId);
+    friend void generateAtomLogging(LogEventQueue& queue, const LogEventFilter& filter,
+                                    int eventCount, int startAtomId);
 
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessage);
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessageEmptySetExplicitSet);
diff --git a/statsd/src/state/StateTracker.h b/statsd/src/state/StateTracker.h
index 0fd504d..8e8f27f 100644
--- a/statsd/src/state/StateTracker.h
+++ b/statsd/src/state/StateTracker.h
@@ -16,13 +16,14 @@
 #pragma once
 
 #include <utils/RefBase.h>
+
+#include <set>
+#include <unordered_map>
+
 #include "HashableDimensionKey.h"
 #include "logd/LogEvent.h"
-
 #include "state/StateListener.h"
 
-#include <unordered_map>
-
 namespace android {
 namespace os {
 namespace statsd {
diff --git a/statsd/src/stats_log.proto b/statsd/src/stats_log.proto
index 7354f37..93b0d8c 100644
--- a/statsd/src/stats_log.proto
+++ b/statsd/src/stats_log.proto
@@ -318,6 +318,10 @@
 
   optional bool dimension_guardrail_hit = 17;
 
+  optional int64 estimated_data_bytes = 18;
+
+  repeated DataCorruptedReason data_corrupted_reason = 19;
+
   // Do not use.
   reserved 13, 15;
 }
@@ -403,6 +407,8 @@
   reserved 10;
 
   repeated DataCorruptedReason data_corrupted_reason = 11;
+
+  optional int64 estimated_data_bytes = 12;
 }
 
 message ConfigMetricsReportList {
@@ -499,6 +505,7 @@
         optional int32 db_deletion_too_old = 35;
         optional int32 db_deletion_config_removed = 36;
         optional int32 db_deletion_config_updated = 37;
+        optional int32 config_metadata_provider_promotion_failed = 38;
     }
 
     repeated ConfigStats config_stats = 3;
@@ -691,6 +698,17 @@
     }
 
     optional EventQueueStats event_queue_stats = 25;
+
+    // Tracks info about reads from the socket.
+    message SocketReadStats {
+        // This is a histogram of sizes of the batched reads. The bins are as follows:
+        // [0, 1, 2, 3, 4, 5-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-69, 70-79, 80-89, 90-99,
+        //  100-199, 200-299, 300-399, 400-499, 500-599, 600-699, 700-799, 800-899, 900-999,
+        //  1000-1199, 1200-1399, 1400-1599, 1600-1799, 1800-1999, 2000+]
+        repeated int64 batched_read_size = 1;
+    }
+
+    optional SocketReadStats socket_read_stats = 26;
 }
 
 message AlertTriggerDetails {
diff --git a/statsd/src/stats_log_util.cpp b/statsd/src/stats_log_util.cpp
index 6d47344..f7c9996 100644
--- a/statsd/src/stats_log_util.cpp
+++ b/statsd/src/stats_log_util.cpp
@@ -554,6 +554,18 @@
     protoOutput->end(token);
 }
 
+void writeDataCorruptedReasons(ProtoOutputStream& proto, int fieldIdDataCorruptedReason,
+                               bool hasQueueOverflow, bool hasSocketLoss) {
+    if (hasQueueOverflow) {
+        proto.write(FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED | fieldIdDataCorruptedReason,
+                    DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW);
+    }
+    if (hasSocketLoss) {
+        proto.write(FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED | fieldIdDataCorruptedReason,
+                    DATA_CORRUPTED_SOCKET_LOSS);
+    }
+}
+
 int64_t getElapsedRealtimeNs() {
     return ::android::elapsedRealtimeNano();
 }
diff --git a/statsd/src/stats_log_util.h b/statsd/src/stats_log_util.h
index 16cdb35..5aa4c0b 100644
--- a/statsd/src/stats_log_util.h
+++ b/statsd/src/stats_log_util.h
@@ -92,6 +92,9 @@
 void writeAtomMetricStatsToStream(const std::pair<int64_t, StatsdStats::AtomMetricStats> &pair,
                                   ProtoOutputStream *protoOutput);
 
+void writeDataCorruptedReasons(ProtoOutputStream& proto, int fieldIdDataCorruptedReason,
+                               bool hasQueueOverflow, bool hasSocketLoss);
+
 template<class T>
 bool parseProtoOutputStream(ProtoOutputStream& protoOutput, T* message) {
     std::string pbBytes;
diff --git a/statsd/src/statsd_config.proto b/statsd/src/statsd_config.proto
index a7eef69..8d48df0 100644
--- a/statsd/src/statsd_config.proto
+++ b/statsd/src/statsd_config.proto
@@ -358,6 +358,8 @@
 
   optional int32 sampling_percentage = 17 [default = 100];
 
+  optional int32 pull_probability = 18 [default = 100];
+
   reserved 100;
   reserved 101;
 }
@@ -393,6 +395,8 @@
   }
   optional AggregationType aggregation_type = 8 [default = SUM];
 
+  repeated AggregationType aggregation_types = 25;
+
   optional bool include_sample_size = 22;
 
   optional int64 min_bucket_size_nanos = 10;
@@ -509,6 +513,15 @@
   optional bytes trace_config = 1;
 }
 
+message UprobestatsDetails {
+  // The |config| field is a proto-encoded message of type
+  // uprobestats.protos.UprobestatsConfig defined in
+  // //packages/modules/UprobeStats/src/config.proto. On device,
+  // statsd doesn't need to deserialize the message as it's just
+  // passed binary-encoded to the Uprobestats API.
+  optional bytes config = 1;
+}
+
 message BroadcastSubscriberDetails {
   optional int64 subscriber_id = 1;
   repeated string cookie = 2;
@@ -530,6 +543,7 @@
     IncidentdDetails incidentd_details = 4;
     PerfettoDetails perfetto_details = 5;
     BroadcastSubscriberDetails broadcast_subscriber_details = 6;
+    UprobestatsDetails uprobestats_details = 9;
   }
 
   optional float probability_of_informing = 7 [default = 1.1];
@@ -628,6 +642,12 @@
 
   optional int32 soft_metrics_memory_kb = 29;
 
+  message StatsdConfigOptions {
+    optional bool use_v2_soft_memory_limit = 1;
+  }
+
+  optional StatsdConfigOptions statsd_config_options = 30;
+
   // Do not use.
   reserved 1000, 1001;
 }
diff --git a/statsd/src/utils/api_tracing.h b/statsd/src/utils/api_tracing.h
new file mode 100644
index 0000000..69edeae
--- /dev/null
+++ b/statsd/src/utils/api_tracing.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+#define ATRACE_TAG ATRACE_TAG_APP
+
+#include <utils/Trace.h>
diff --git a/statsd/tests/FieldValue_test.cpp b/statsd/tests/FieldValue_test.cpp
index cf1e5a5..8e9f789 100644
--- a/statsd/tests/FieldValue_test.cpp
+++ b/statsd/tests/FieldValue_test.cpp
@@ -542,6 +542,124 @@
     }
 }
 
+TEST(AtomMatcherTest, TestDedupFieldMatchersAllDifferent) {
+    // Matchers: Fields 1, 2, 3
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(3);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(3, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 1, 2, 3
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)3, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+    EXPECT_EQ(fieldMatchers[1], dedupedFieldMatchers[1]);
+    EXPECT_EQ(fieldMatchers[2], dedupedFieldMatchers[2]);
+}
+
+TEST(AtomMatcherTest, TestDedupFieldMatchersAllSame) {
+    // Matcher: Fields 1, 1, 1
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(3, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 1, 1, 1
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)1, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+}
+
+TEST(AtomMatcherTest, TestDedupFieldMatcherMixOfFields) {
+    // Matcher: Fields 2, 2, 1, 3, 2, 1, 3
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(3);
+    child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(3);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(7, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 2, 1, 3
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)3, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+    EXPECT_EQ(fieldMatchers[2], dedupedFieldMatchers[1]);
+    EXPECT_EQ(fieldMatchers[3], dedupedFieldMatchers[2]);
+}
+
+TEST(AtomMatcherTest, TestDedupFieldMatcherDifferentPositionSameFields) {
+    // Matcher: Fields 3, 1.1(FIRST), 1.2(FIRST), 1.1(FIRST), 1.1(LAST), 1.2(FIRST), 2
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(3);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::LAST);
+    child->add_child()->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(2);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(7, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 3, 1.1(FIRST), 1.2(FIRST), 1.1(LAST) 2
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)5, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+    EXPECT_EQ(fieldMatchers[1], dedupedFieldMatchers[1]);
+    EXPECT_EQ(fieldMatchers[2], dedupedFieldMatchers[2]);
+    EXPECT_EQ(fieldMatchers[4], dedupedFieldMatchers[3]);
+    EXPECT_EQ(fieldMatchers[6], dedupedFieldMatchers[4]);
+}
+
 void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel,
                                                  int32_t nodeDepthInAttributionChain,
                                                  int32_t uid, string tag) {
diff --git a/statsd/tests/MetricsManager_test.cpp b/statsd/tests/MetricsManager_test.cpp
index d86e975..a45fa24 100644
--- a/statsd/tests/MetricsManager_test.cpp
+++ b/statsd/tests/MetricsManager_test.cpp
@@ -32,6 +32,7 @@
 #include "src/state/StateManager.h"
 #include "src/statsd_config.pb.h"
 #include "statsd_test_util.h"
+#include "statslog_statsdtest.h"
 
 using namespace testing;
 using android::sp;
@@ -53,10 +54,12 @@
 
 const long timeBaseSec = 1000;
 
-StatsdConfig buildGoodRestrictedConfig() {
+StatsdConfig buildEventConfig(bool isRestricted) {
     StatsdConfig config;
     config.set_id(kConfigId);
-    config.set_restricted_metrics_delegate_package_name("delegate");
+    if (isRestricted) {
+        config.set_restricted_metrics_delegate_package_name("delegate");
+    }
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
     eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
@@ -69,6 +72,14 @@
     return config;
 }
 
+StatsdConfig buildGoodRestrictedConfig() {
+    return buildEventConfig(/*isRestricted*/ true);
+}
+
+StatsdConfig buildGoodEventConfig() {
+    return buildEventConfig(/*isRestricted*/ false);
+}
+
 set<int32_t> unionSet(const vector<set<int32_t>> sets) {
     set<int32_t> toRet;
     for (const set<int32_t>& s : sets) {
@@ -76,6 +87,36 @@
     }
     return toRet;
 }
+
+SocketLossInfo createSocketLossInfo(int32_t uid, int32_t atomId) {
+    SocketLossInfo lossInfo;
+    lossInfo.uid = uid;
+    lossInfo.errors.push_back(-11);
+    lossInfo.atomIds.push_back(atomId);
+    lossInfo.counts.push_back(1);
+    return lossInfo;
+}
+
+// helper API to create STATS_SOCKET_LOSS_REPORTED LogEvent
+LogEvent createSocketLossInfoLogEvent(int32_t uid, int32_t lossAtomId) {
+    const SocketLossInfo lossInfo = createSocketLossInfo(uid, lossAtomId);
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::STATS_SOCKET_LOSS_REPORTED);
+    AStatsEvent_writeInt32(statsEvent, lossInfo.uid);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_writeInt64(statsEvent, lossInfo.firstLossTsNanos);
+    AStatsEvent_writeInt64(statsEvent, lossInfo.lastLossTsNanos);
+    AStatsEvent_writeInt32(statsEvent, lossInfo.overflowCounter);
+    AStatsEvent_writeInt32Array(statsEvent, lossInfo.errors.data(), lossInfo.errors.size());
+    AStatsEvent_writeInt32Array(statsEvent, lossInfo.atomIds.data(), lossInfo.atomIds.size());
+    AStatsEvent_writeInt32Array(statsEvent, lossInfo.counts.data(), lossInfo.counts.size());
+
+    LogEvent event(uid /* uid */, 0 /* pid */);
+    parseStatsEventToLogEvent(statsEvent, &event);
+    return event;
+}
+
 }  // anonymous namespace
 
 TEST(MetricsManagerTest, TestLogSources) {
@@ -299,6 +340,69 @@
     EXPECT_TRUE(metricsManager.checkLogCredentials(event));
 }
 
+TEST(MetricsManagerTest, TestOnLogEventLossForAllowedFromAnyUidAtom) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodEventConfig();
+    config.add_whitelisted_atom_ids(2);
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    const int32_t customAppUid = AID_APP_START + 1;
+    LogEvent eventOfInterest(customAppUid /* uid */, 0 /* pid */);
+    CreateNoValuesLogEvent(&eventOfInterest, 2 /* atom id */, 0 /* timestamp */);
+    EXPECT_TRUE(metricsManager.checkLogCredentials(eventOfInterest));
+    EXPECT_FALSE(metricsManager.mAllMetricProducers[0]->mDataCorruptedDueToSocketLoss);
+
+    LogEvent eventSocketLossReported = createSocketLossInfoLogEvent(customAppUid, 2);
+
+    // the STATS_SOCKET_LOSS_REPORTED on its own will not be propagated/consumed by any metric
+    EXPECT_FALSE(metricsManager.checkLogCredentials(eventSocketLossReported));
+
+    // the loss info for an atom of interest (2) will be evaluated even when
+    // STATS_SOCKET_LOSS_REPORTED atom is not explicitly in allowed list
+    metricsManager.onLogEvent(eventSocketLossReported);
+
+    // check that corresponding event metric was updated with loss info
+    // the invariant is there is only one metric in the config
+    EXPECT_TRUE(metricsManager.mAllMetricProducers[0]->mDataCorruptedDueToSocketLoss);
+}
+
+TEST(MetricsManagerTest, TestOnLogEventLossForNotAllowedAtom) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodEventConfig();
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    const int32_t customAppUid = AID_APP_START + 1;
+    LogEvent eventOfInterest(customAppUid /* uid */, 0 /* pid */);
+    CreateNoValuesLogEvent(&eventOfInterest, 2 /* atom id */, 0 /* timestamp */);
+    EXPECT_FALSE(metricsManager.checkLogCredentials(eventOfInterest));
+    EXPECT_FALSE(metricsManager.mAllMetricProducers[0]->mDataCorruptedDueToSocketLoss);
+
+    LogEvent eventSocketLossReported = createSocketLossInfoLogEvent(customAppUid, 2);
+
+    // the STATS_SOCKET_LOSS_REPORTED on its own will not be propagated/consumed by any metric
+    EXPECT_FALSE(metricsManager.checkLogCredentials(eventSocketLossReported));
+
+    // the loss info for an atom of interest (2) will not be evaluated since atom of interest does
+    // not pass credential check
+    metricsManager.onLogEvent(eventSocketLossReported);
+
+    // check that corresponding event metric was not updated with loss info
+    // the invariant is there is only one metric in the config
+    EXPECT_FALSE(metricsManager.mAllMetricProducers[0]->mDataCorruptedDueToSocketLoss);
+}
+
 TEST(MetricsManagerTest, TestWhitelistedAtomStateTracker) {
     sp<UidMap> uidMap;
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
diff --git a/statsd/tests/SocketListener_test.cpp b/statsd/tests/SocketListener_test.cpp
index 3636508..cc5475e 100644
--- a/statsd/tests/SocketListener_test.cpp
+++ b/statsd/tests/SocketListener_test.cpp
@@ -55,27 +55,24 @@
 
 }  //  namespace
 
-void generateAtomLogging(const std::shared_ptr<LogEventQueue>& queue,
-                         const std::shared_ptr<LogEventFilter>& filter, int eventCount,
+void generateAtomLogging(LogEventQueue& queue, const LogEventFilter& filter, int eventCount,
                          int startAtomId) {
     // create number of AStatsEvent
     for (int i = 0; i < eventCount; i++) {
         AStatsEventWrapper event(startAtomId + i);
         auto [buf, size] = event.getBuffer();
-        StatsSocketListener::processMessage(buf, size, kTestUid, kTestPid, queue, filter);
+        StatsSocketListener::processStatsEventBuffer(buf, size, kTestUid, kTestPid, queue, filter);
     }
 }
 
 class SocketParseMessageTest : public testing::TestWithParam<bool> {
 protected:
-    std::shared_ptr<LogEventQueue> mEventQueue;
-    std::shared_ptr<LogEventFilter> mLogEventFilter;
+    LogEventQueue mEventQueue;
+    LogEventFilter mLogEventFilter;
 
 public:
-    SocketParseMessageTest()
-        : mEventQueue(std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/)),
-          mLogEventFilter(std::make_shared<LogEventFilter>()) {
-        mLogEventFilter->setFilteringEnabled(GetParam());
+    SocketParseMessageTest() : mEventQueue(kEventCount /*buffer limit*/) {
+        mLogEventFilter.setFilteringEnabled(GetParam());
     }
 
     static std::string ToString(testing::TestParamInfo<bool> info) {
@@ -93,9 +90,9 @@
 
     int64_t lastEventTs = 0;
     // check content of the queue
-    EXPECT_EQ(kEventCount, mEventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount, mEventQueue.mQueue.size());
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = mEventQueue->waitPop();
+        auto logEvent = mEventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_EQ(logEvent->isParsedHeaderOnly(), GetParam());
@@ -108,13 +105,13 @@
 
 TEST_P(SocketParseMessageTest, TestProcessMessageEmptySetExplicitSet) {
     LogEventFilter::AtomIdSet idsList;
-    mLogEventFilter->setAtomIds(idsList, nullptr);
+    mLogEventFilter.setAtomIds(idsList, nullptr);
     generateAtomLogging(mEventQueue, mLogEventFilter, kEventCount, kAtomId);
 
     // check content of the queue
-    EXPECT_EQ(kEventCount, mEventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount, mEventQueue.mQueue.size());
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = mEventQueue->waitPop();
+        auto logEvent = mEventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_EQ(logEvent->isParsedHeaderOnly(), GetParam());
@@ -122,23 +119,22 @@
 }
 
 TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+    LogEventQueue eventQueue(kEventCount /*buffer limit*/);
 
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+    LogEventFilter logEventFilter;
 
     LogEventFilter::AtomIdSet idsList;
     for (int i = 0; i < kEventCount; i++) {
         idsList.insert(kAtomId + i);
     }
-    logEventFilter->setAtomIds(idsList, nullptr);
+    logEventFilter.setAtomIds(idsList, nullptr);
 
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
 
     // check content of the queue
-    EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount, eventQueue.mQueue.size());
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
@@ -146,30 +142,29 @@
 }
 
 TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+    LogEventQueue eventQueue(kEventCount /*buffer limit*/);
 
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+    LogEventFilter logEventFilter;
 
     LogEventFilter::AtomIdSet idsList;
     for (int i = 0; i < kEventFilteredCount; i++) {
         idsList.insert(kAtomId + i);
     }
-    logEventFilter->setAtomIds(idsList, nullptr);
+    logEventFilter.setAtomIds(idsList, nullptr);
 
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
 
     // check content of the queue
-    EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount, eventQueue.mQueue.size());
     for (int i = 0; i < kEventFilteredCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
     }
 
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_TRUE(logEvent->isParsedHeaderOnly());
@@ -177,41 +172,40 @@
 }
 
 TEST(SocketParseMessageTest, TestProcessMessageFilterToggle) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount * 3 /*buffer limit*/);
+    LogEventQueue eventQueue(kEventCount * 3 /*buffer limit*/);
 
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+    LogEventFilter logEventFilter;
 
     LogEventFilter::AtomIdSet idsList;
     for (int i = 0; i < kEventFilteredCount; i++) {
         idsList.insert(kAtomId + i);
     }
     // events with ids from kAtomId to kAtomId + kEventFilteredCount should not be skipped
-    logEventFilter->setAtomIds(idsList, nullptr);
+    logEventFilter.setAtomIds(idsList, nullptr);
 
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
 
-    logEventFilter->setFilteringEnabled(false);
+    logEventFilter.setFilteringEnabled(false);
     // since filtering is disabled - events with any ids should not be skipped
     // will generate events with ids [kAtomId + kEventCount, kAtomId + kEventCount * 2]
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId + kEventCount);
 
-    logEventFilter->setFilteringEnabled(true);
+    logEventFilter.setFilteringEnabled(true);
     LogEventFilter::AtomIdSet idsList2;
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
         idsList2.insert(kAtomId + kEventCount * 2 + i);
     }
     // events with idsList2 ids should not be skipped
-    logEventFilter->setAtomIds(idsList2, nullptr);
+    logEventFilter.setAtomIds(idsList2, nullptr);
 
     // will generate events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId + kEventCount * 2);
 
     // check content of the queue
-    EXPECT_EQ(kEventCount * 3, eventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount * 3, eventQueue.mQueue.size());
     // events with ids from kAtomId to kAtomId + kEventFilteredCount should not be skipped
     for (int i = 0; i < kEventFilteredCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
@@ -219,7 +213,7 @@
 
     // all events above kAtomId + kEventFilteredCount to kAtomId + kEventCount should be skipped
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_TRUE(logEvent->isParsedHeaderOnly());
@@ -228,7 +222,7 @@
     // events with ids [kAtomId + kEventCount, kAtomId + kEventCount * 2] should not be skipped
     // since wiltering was disabled at that time
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + kEventCount + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
@@ -237,7 +231,7 @@
     // first half events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
     // should be skipped
     for (int i = 0; i < kEventFilteredCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
         EXPECT_TRUE(logEvent->isParsedHeaderOnly());
@@ -246,7 +240,7 @@
     // second half events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
     // should be processed
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
diff --git a/statsd/tests/e2e/CountMetric_e2e_test.cpp b/statsd/tests/e2e/CountMetric_e2e_test.cpp
index a35312f..f7e5aea 100644
--- a/statsd/tests/e2e/CountMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -251,6 +251,7 @@
 
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
     StatsLogReport::CountMetricDataWrapper countMetrics;
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index a6d50d9..308ef52 100644
--- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -94,6 +94,7 @@
     backfillStartEndTimestamp(&reports);
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
     EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
 
diff --git a/statsd/tests/e2e/EventMetric_e2e_test.cpp b/statsd/tests/e2e/EventMetric_e2e_test.cpp
index 0432f3c..6909dc2 100644
--- a/statsd/tests/e2e/EventMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/EventMetric_e2e_test.cpp
@@ -84,8 +84,10 @@
     ASSERT_EQ(reports.reports_size(), 1);
 
     ConfigMetricsReport report = reports.reports(0);
+    EXPECT_TRUE(report.has_estimated_data_bytes());
     ASSERT_EQ(report.metrics_size(), 1);
     StatsLogReport wakelockEventMetricReport = report.metrics(0);
+    EXPECT_TRUE(wakelockEventMetricReport.has_estimated_data_bytes());
     EXPECT_EQ(wakelockEventMetricReport.metric_id(), wakelockEventMetric.id());
     EXPECT_TRUE(wakelockEventMetricReport.has_event_metrics());
     ASSERT_EQ(wakelockEventMetricReport.event_metrics().data_size(), 3);
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index e58f813..e9d5b0e 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -145,6 +145,7 @@
     backfillAggregatedAtoms(&reports);
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
     ASSERT_GT((int)gaugeMetrics.data_size(), 1);
@@ -955,6 +956,420 @@
     EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0);
 }
 
+TEST(GaugeMetricE2ePulledTest, TestGaugeMetricPullProbabilityWithTriggerEvent) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    auto config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES, /*useCondition=*/false);
+    auto gaugeMetric = config.mutable_gauge_metric(0);
+    gaugeMetric->set_pull_probability(50);
+    auto triggerEventMatcher = CreateScreenTurnedOnAtomMatcher();
+    gaugeMetric->set_trigger_event(triggerEventMatcher.id());
+    gaugeMetric->set_max_num_gauge_atoms_per_bucket(200);
+    gaugeMetric->set_bucket(ONE_HOUR);
+
+    int64_t configAddedTimeNs = 60 * NS_PER_SEC;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor =
+            CreateStatsLogProcessor(configAddedTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    // First bucket events.
+    for (int i = 0; i < 30; i++) {
+        events.push_back(CreateScreenStateChangedEvent(configAddedTimeNs + (i * 10 * NS_PER_SEC),
+                                                       android::view::DISPLAY_STATE_ON));
+    }
+    // Second bucket events.
+    for (int i = 0; i < 30; i++) {
+        events.push_back(CreateScreenStateChangedEvent(
+                configAddedTimeNs + bucketSizeNs + (i * 10 * NS_PER_SEC),
+                android::view::DISPLAY_STATE_ON));
+    }
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true,
+                            ADB_DUMP, FAST, &buffer);
+
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ((int)gaugeMetrics.data_size(), 2);  // 2 sets of data for each pull.
+
+    // Data 1
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_1",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(2, data.bucket_info_size());
+
+    // Data 1, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+
+    // Data 1, Bucket 2
+    ASSERT_EQ(18, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(1), configAddedTimeNs + bucketSizeNs,
+            configAddedTimeNs + 2 * bucketSizeNs,
+            {(int64_t)3660 * NS_PER_SEC, (int64_t)3680 * NS_PER_SEC, (int64_t)3700 * NS_PER_SEC,
+             (int64_t)3710 * NS_PER_SEC, (int64_t)3720 * NS_PER_SEC, (int64_t)3740 * NS_PER_SEC,
+             (int64_t)3780 * NS_PER_SEC, (int64_t)3790 * NS_PER_SEC, (int64_t)3820 * NS_PER_SEC,
+             (int64_t)3850 * NS_PER_SEC, (int64_t)3860 * NS_PER_SEC, (int64_t)3870 * NS_PER_SEC,
+             (int64_t)3880 * NS_PER_SEC, (int64_t)3900 * NS_PER_SEC, (int64_t)3910 * NS_PER_SEC,
+             (int64_t)3920 * NS_PER_SEC, (int64_t)3930 * NS_PER_SEC, (int64_t)3940 * NS_PER_SEC});
+
+    // Data 2
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_2",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(2, data.bucket_info_size());
+
+    // Data 2, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+
+    // Data 2, Bucket 2
+    ASSERT_EQ(18, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(1), configAddedTimeNs + bucketSizeNs,
+            configAddedTimeNs + 2 * bucketSizeNs,
+            {(int64_t)3660 * NS_PER_SEC, (int64_t)3680 * NS_PER_SEC, (int64_t)3700 * NS_PER_SEC,
+             (int64_t)3710 * NS_PER_SEC, (int64_t)3720 * NS_PER_SEC, (int64_t)3740 * NS_PER_SEC,
+             (int64_t)3780 * NS_PER_SEC, (int64_t)3790 * NS_PER_SEC, (int64_t)3820 * NS_PER_SEC,
+             (int64_t)3850 * NS_PER_SEC, (int64_t)3860 * NS_PER_SEC, (int64_t)3870 * NS_PER_SEC,
+             (int64_t)3880 * NS_PER_SEC, (int64_t)3900 * NS_PER_SEC, (int64_t)3910 * NS_PER_SEC,
+             (int64_t)3920 * NS_PER_SEC, (int64_t)3930 * NS_PER_SEC, (int64_t)3940 * NS_PER_SEC});
+}
+
+TEST(GaugeMetricE2ePulledTest, TestGaugeMetricPullProbabilityWithBucketBoundaryAlarm) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    auto config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES, /*useCondition=*/false);
+    auto gaugeMetric = config.mutable_gauge_metric(0);
+    gaugeMetric->set_pull_probability(50);
+    gaugeMetric->set_max_num_gauge_atoms_per_bucket(200);
+
+    int64_t baseTimeNs = 5 * 60 * NS_PER_SEC;
+    int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor =
+            CreateStatsLogProcessor(configAddedTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+
+    // Pulling alarm arrives on time and resets the sequential pulling alarm.
+    for (int i = 1; i < 31; i++) {
+        processor->informPullAlarmFired(configAddedTimeNs + i * bucketSizeNs);
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 32 * bucketSizeNs + 10, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ((int)gaugeMetrics.data_size(), 2);
+
+    // Data 1
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_1",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(14, data.bucket_info_size());
+
+    EXPECT_EQ(1, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs, {configAddedTimeNs});
+
+    EXPECT_EQ(1, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(1), configAddedTimeNs + 2 * bucketSizeNs,
+                             configAddedTimeNs + 3 * bucketSizeNs,
+                             {configAddedTimeNs + 2 * bucketSizeNs});  // 1200000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(2).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(2), configAddedTimeNs + 3 * bucketSizeNs,
+                             configAddedTimeNs + 4 * bucketSizeNs,
+                             {(int64_t)configAddedTimeNs + 3 * bucketSizeNs});  // 1500000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(3).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(3), configAddedTimeNs + 7 * bucketSizeNs,
+                             configAddedTimeNs + 8 * bucketSizeNs,
+                             {configAddedTimeNs + 7 * bucketSizeNs});  // 2700000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(4).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(4), configAddedTimeNs + 9 * bucketSizeNs,
+                             configAddedTimeNs + 10 * bucketSizeNs,
+                             {configAddedTimeNs + 9 * bucketSizeNs});  // 3300000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(5).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(5), configAddedTimeNs + 11 * bucketSizeNs,
+                             configAddedTimeNs + 12 * bucketSizeNs,
+                             {configAddedTimeNs + 11 * bucketSizeNs});  // 3900000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(6).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(6), configAddedTimeNs + 13 * bucketSizeNs,
+                             configAddedTimeNs + 14 * bucketSizeNs,
+                             {configAddedTimeNs + 13 * bucketSizeNs});  // 4500000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(7).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(7), configAddedTimeNs + 14 * bucketSizeNs,
+                             configAddedTimeNs + 15 * bucketSizeNs,
+                             {configAddedTimeNs + 14 * bucketSizeNs});  // 4800000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(8).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(8), configAddedTimeNs + 18 * bucketSizeNs,
+                             configAddedTimeNs + 19 * bucketSizeNs,
+                             {configAddedTimeNs + 18 * bucketSizeNs});  // 6000000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(9).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(9), configAddedTimeNs + 19 * bucketSizeNs,
+                             configAddedTimeNs + 20 * bucketSizeNs,
+                             {configAddedTimeNs + 19 * bucketSizeNs});  // 6300000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(10).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(10), configAddedTimeNs + 24 * bucketSizeNs,
+                             configAddedTimeNs + 25 * bucketSizeNs,
+                             {configAddedTimeNs + 24 * bucketSizeNs});  // 7800000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(11).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(11), configAddedTimeNs + 27 * bucketSizeNs,
+                             configAddedTimeNs + 28 * bucketSizeNs,
+                             {configAddedTimeNs + 27 * bucketSizeNs});  // 8700000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(12).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(12), configAddedTimeNs + 28 * bucketSizeNs,
+                             configAddedTimeNs + 29 * bucketSizeNs,
+                             {configAddedTimeNs + 28 * bucketSizeNs});  // 9000000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(13).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(13), configAddedTimeNs + 30 * bucketSizeNs,
+                             configAddedTimeNs + 31 * bucketSizeNs,
+                             {configAddedTimeNs + 30 * bucketSizeNs});  // 9600000000000ns
+
+    // Data 2
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_2",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(14, data.bucket_info_size());
+
+    EXPECT_EQ(1, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs, {configAddedTimeNs});
+
+    EXPECT_EQ(1, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(1), configAddedTimeNs + 2 * bucketSizeNs,
+                             configAddedTimeNs + 3 * bucketSizeNs,
+                             {configAddedTimeNs + 2 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(2).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(2), configAddedTimeNs + 3 * bucketSizeNs,
+                             configAddedTimeNs + 4 * bucketSizeNs,
+                             {(int64_t)configAddedTimeNs + 3 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(3).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(3), configAddedTimeNs + 7 * bucketSizeNs,
+                             configAddedTimeNs + 8 * bucketSizeNs,
+                             {configAddedTimeNs + 7 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(4).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(4), configAddedTimeNs + 9 * bucketSizeNs,
+                             configAddedTimeNs + 10 * bucketSizeNs,
+                             {configAddedTimeNs + 9 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(5).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(5), configAddedTimeNs + 11 * bucketSizeNs,
+                             configAddedTimeNs + 12 * bucketSizeNs,
+                             {configAddedTimeNs + 11 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(6).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(6), configAddedTimeNs + 13 * bucketSizeNs,
+                             configAddedTimeNs + 14 * bucketSizeNs,
+                             {configAddedTimeNs + 13 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(7).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(7), configAddedTimeNs + 14 * bucketSizeNs,
+                             configAddedTimeNs + 15 * bucketSizeNs,
+                             {configAddedTimeNs + 14 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(8).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(8), configAddedTimeNs + 18 * bucketSizeNs,
+                             configAddedTimeNs + 19 * bucketSizeNs,
+                             {configAddedTimeNs + 18 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(9).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(9), configAddedTimeNs + 19 * bucketSizeNs,
+                             configAddedTimeNs + 20 * bucketSizeNs,
+                             {configAddedTimeNs + 19 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(10).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(10), configAddedTimeNs + 24 * bucketSizeNs,
+                             configAddedTimeNs + 25 * bucketSizeNs,
+                             {configAddedTimeNs + 24 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(11).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(11), configAddedTimeNs + 27 * bucketSizeNs,
+                             configAddedTimeNs + 28 * bucketSizeNs,
+                             {configAddedTimeNs + 27 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(12).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(12), configAddedTimeNs + 28 * bucketSizeNs,
+                             configAddedTimeNs + 29 * bucketSizeNs,
+                             {configAddedTimeNs + 28 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(13).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(13), configAddedTimeNs + 30 * bucketSizeNs,
+                             configAddedTimeNs + 31 * bucketSizeNs,
+                             {configAddedTimeNs + 30 * bucketSizeNs});
+}
+
+TEST(GaugeMetricE2ePulledTest, TestGaugeMetricPullProbabilityWithCondition) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE, /*useCondition=*/true);
+    auto gaugeMetric = config.mutable_gauge_metric(0);
+    gaugeMetric->set_pull_probability(50);
+    gaugeMetric->set_max_num_gauge_atoms_per_bucket(200);
+    gaugeMetric->set_bucket(ONE_HOUR);
+
+    int64_t configAddedTimeNs = 60 * NS_PER_SEC;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor =
+            CreateStatsLogProcessor(configAddedTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    // First bucket events.
+    for (int i = 0; i < 30; i++) {
+        events.push_back(CreateScreenStateChangedEvent(configAddedTimeNs + (i * 10 * NS_PER_SEC),
+                                                       android::view::DISPLAY_STATE_OFF));
+        events.push_back(CreateScreenStateChangedEvent(configAddedTimeNs + (i * 11 * NS_PER_SEC),
+                                                       android::view::DISPLAY_STATE_ON));
+    }
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs, false, true, ADB_DUMP,
+                            FAST, &buffer);
+
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ((int)gaugeMetrics.data_size(), 2);  // 2 sets of data for each pull.
+
+    // Data 1
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_1",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(1, data.bucket_info_size());
+
+    // Data 1, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+
+    // Data 2
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_2",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(1, data.bucket_info_size());
+
+    // Data 2, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index b852d7f..1f35564 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -195,6 +195,7 @@
         backfillAggregatedAtoms(&reports);
         ASSERT_EQ(1, reports.reports_size());
         ASSERT_EQ(1, reports.reports(0).metrics_size());
+        EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
         StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
         sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(),
                                         &gaugeMetrics);
diff --git a/statsd/tests/e2e/KllMetric_e2e_test.cpp b/statsd/tests/e2e/KllMetric_e2e_test.cpp
index a4f7cc4..7cb5807 100644
--- a/statsd/tests/e2e/KllMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/KllMetric_e2e_test.cpp
@@ -91,6 +91,7 @@
     ConfigMetricsReport report = reports.reports(0);
     ASSERT_EQ(report.metrics_size(), 1);
     StatsLogReport metricReport = report.metrics(0);
+    EXPECT_TRUE(metricReport.has_estimated_data_bytes());
     EXPECT_EQ(metricReport.metric_id(), metric.id());
     EXPECT_TRUE(metricReport.has_kll_metrics());
     ASSERT_EQ(metricReport.kll_metrics().data_size(), 1);
diff --git a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index ab0e8ea..323ed17 100644
--- a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -274,6 +274,7 @@
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
     ASSERT_GT((int)valueMetrics.data_size(), 1);
 
@@ -830,6 +831,76 @@
     ASSERT_EQ(0, processor->mMetricsManagers.size());
 }
 
+TEST(ValueMetricE2eTest, TestInitWithMultipleAggTypes) {
+    // Create config.
+    StatsdConfig config;
+
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    // Create value metric.
+    int64_t metricId = 123456;
+    ValueMetric* valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(testAtomReportedMatcher.id());
+    *valueMetric->mutable_value_field() = CreateDimensions(
+            util::TEST_ATOM_REPORTED, {2 /*int_field*/, 2 /*int_field*/, 3 /*long_field*/,
+                                       3 /*long_field*/, 3 /*long_field*/});
+    valueMetric->add_aggregation_types(ValueMetric::SUM);
+    valueMetric->add_aggregation_types(ValueMetric::MIN);
+    valueMetric->add_aggregation_types(ValueMetric::MAX);
+    valueMetric->add_aggregation_types(ValueMetric::AVG);
+    valueMetric->add_aggregation_types(ValueMetric::MIN);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(1, metricsManager->mAllMetricProducers.size());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+}
+
+TEST(ValueMetricE2eTest, TestInitWithDefaultAggType) {
+    // Create config.
+    StatsdConfig config;
+
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    // Create value metric.
+    int64_t metricId = 123456;
+    ValueMetric* valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(testAtomReportedMatcher.id());
+    *valueMetric->mutable_value_field() =
+            CreateDimensions(util::TEST_ATOM_REPORTED, {3 /*long_field*/, 2 /*int_field*/});
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(1, metricsManager->mAllMetricProducers.size());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/guardrail/StatsdStats_test.cpp b/statsd/tests/guardrail/StatsdStats_test.cpp
index 1d56caa..9232f5b 100644
--- a/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -88,6 +88,20 @@
     EXPECT_FALSE(configReport.has_deletion_time_sec());
 }
 
+TEST(StatsdStatsTest, TestConfigMetadataProviderPromotionFailed) {
+    StatsdStats stats;
+    ConfigKey key(0, 12345);
+    stats.noteConfigReceived(key, /*metricsCount=*/0, /*conditionsCount=*/0, /*matchersCount=*/0,
+                             /*alertCount=*/0, /*annotations=*/{}, nullopt /*valid config*/);
+
+    stats.noteConfigMetadataProviderPromotionFailed(key);
+
+    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+    ASSERT_EQ(1, report.config_stats_size());
+    const auto& configReport = report.config_stats(0);
+    EXPECT_EQ(1, configReport.config_metadata_provider_promotion_failed());
+}
+
 TEST(StatsdStatsTest, TestInvalidConfigAdd) {
     StatsdStats stats;
     ConfigKey key(0, 12345);
@@ -1100,6 +1114,47 @@
     }
 }
 
+TEST(StatsdStatsTest, TestSocketBatchReadStats) {
+    StatsdStats stats;
+    stats.noteBatchSocketRead(1);        // bin 1
+    stats.noteBatchSocketRead(2);        // bin 2
+    stats.noteBatchSocketRead(2);        // bin 2
+    stats.noteBatchSocketRead(4);        // bin 4
+    stats.noteBatchSocketRead(5);        // bin 5
+    stats.noteBatchSocketRead(9);        // bin 5
+    stats.noteBatchSocketRead(9);        // bin 5
+    stats.noteBatchSocketRead(10);       // bin 6
+    stats.noteBatchSocketRead(19);       // bin 6
+    stats.noteBatchSocketRead(30);       // bin 8
+    stats.noteBatchSocketRead(32);       // bin 8
+    stats.noteBatchSocketRead(39);       // bin 8
+    stats.noteBatchSocketRead(90);       // bin 14
+    stats.noteBatchSocketRead(99);       // bin 14
+    stats.noteBatchSocketRead(100);      // bin 15
+    stats.noteBatchSocketRead(100);      // bin 15
+    stats.noteBatchSocketRead(199);      // bin 15
+    stats.noteBatchSocketRead(200);      // bin 16
+    stats.noteBatchSocketRead(299);      // bin 16
+    stats.noteBatchSocketRead(999);      // bin 23
+    stats.noteBatchSocketRead(1000);     // bin 24
+    stats.noteBatchSocketRead(1199);     // bin 24
+    stats.noteBatchSocketRead(1200);     // bin 25
+    stats.noteBatchSocketRead(1800);     // bin 28
+    stats.noteBatchSocketRead(1999);     // bin 28
+    stats.noteBatchSocketRead(2000);     // bin 29
+    stats.noteBatchSocketRead(1200000);  // bin 29
+
+    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+    EXPECT_THAT(report.socket_read_stats().batched_read_size(),
+                ElementsAre(0, 1, 2, 0, 1, 3, 2, 0, 3, 0, 0, 0, 0, 0, 2, 3, 2, 0, 0, 0, 0, 0, 0, 1,
+                            2, 1, 0, 0, 2, 2));
+
+    stats.reset();
+    report = getStatsdStatsReport(stats, /* reset stats */ false);
+    EXPECT_THAT(report.socket_read_stats().batched_read_size(),
+                AllOf(SizeIs(StatsdStats::kNumBinsInSocketBatchReadHistogram), Each(0)));
+}
+
 TEST_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap, TestGetAtomDimensionKeySizeLimits) {
     const auto& [atomId, defaultHardLimit] = GetParam();
     EXPECT_EQ(StatsdStats::getAtomDimensionKeySizeLimits(atomId, defaultHardLimit),
diff --git a/statsd/tests/log_event/LogEventQueue_test.cpp b/statsd/tests/log_event/LogEventQueue_test.cpp
index acc9c5d..fe6a27a 100644
--- a/statsd/tests/log_event/LogEventQueue_test.cpp
+++ b/statsd/tests/log_event/LogEventQueue_test.cpp
@@ -118,9 +118,9 @@
 TEST(LogEventQueue_test, TestQueueMaxSize) {
     StatsdStats::getInstance().reset();
 
-    std::shared_ptr<LogEventQueue> queue(std::make_shared<LogEventQueue>(50));
-    std::shared_ptr<LogEventFilter> filter(std::make_shared<LogEventFilter>());
-    filter->setFilteringEnabled(false);
+    LogEventQueue queue(50);
+    LogEventFilter filter;
+    filter.setFilteringEnabled(false);
 
     int64_t eventTimeNs = 100;
     int64_t oldestEventNs = 0;
@@ -129,7 +129,7 @@
         auto statsEvent = makeStatsEvent(eventTimeNs);
         size_t bufferSize;
         const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
-        StatsSocketListener::processMessage(buffer, bufferSize, 0, 0, queue, filter);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
         AStatsEvent_release(statsEvent);
         EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved, i + 1);
         EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos, eventTimeNs);
@@ -142,7 +142,7 @@
     // consumer reads the entire queue
     int64_t nextEventTs = 100;
     for (int i = 0; i < 30; i++, nextEventTs++) {
-        auto event = queue->waitPop();
+        auto event = queue.waitPop();
         EXPECT_TRUE(event != nullptr);
         // All events are in right order.
         EXPECT_EQ(nextEventTs, event->GetElapsedTimestampNs());
@@ -154,7 +154,7 @@
         auto statsEvent = makeStatsEvent(eventTimeNs);
         size_t bufferSize;
         const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
-        StatsSocketListener::processMessage(buffer, bufferSize, 0, 0, queue, filter);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
         AStatsEvent_release(statsEvent);
         EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved, lastMaxSizeObserved);
         EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos,
@@ -163,7 +163,7 @@
     }
 
     for (int i = 0; i < 1; i++, nextEventTs++) {
-        auto event = queue->waitPop();
+        auto event = queue.waitPop();
         EXPECT_TRUE(event != nullptr);
         // All events are in right order.
         EXPECT_EQ(nextEventTs, event->GetElapsedTimestampNs());
@@ -176,7 +176,7 @@
         auto statsEvent = makeStatsEvent(eventTimeNs);
         size_t bufferSize;
         const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
-        StatsSocketListener::processMessage(buffer, bufferSize, 0, 0, queue, filter);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
         AStatsEvent_release(statsEvent);
         EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved, lastMaxSizeObserved);
         EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos,
@@ -188,7 +188,7 @@
         auto statsEvent = makeStatsEvent(eventTimeNs);
         size_t bufferSize;
         const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
-        StatsSocketListener::processMessage(buffer, bufferSize, 0, 0, queue, filter);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
         AStatsEvent_release(statsEvent);
         EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved,
                   lastMaxSizeObserved + i + 1);
diff --git a/statsd/tests/metrics/CountMetricProducer_test.cpp b/statsd/tests/metrics/CountMetricProducer_test.cpp
index 32c48b0..8db5bce 100644
--- a/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -75,8 +75,10 @@
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2);
+                                      wizard, protoHash, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2,
+                                      provider);
     EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, countProducer.mCurrentBucketNum);
     EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs());
@@ -94,9 +96,10 @@
     metric.set_bucket(ONE_MINUTE);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs,
+                                      provider);
 
     // 2 events in bucket 1.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -158,8 +161,9 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
     assertConditionTimer(countProducer.mConditionTimer, false, 0, 0);
 
     countProducer.onConditionChanged(true, bucketStartTimeNs);
@@ -229,9 +233,10 @@
 
     EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, 0 /*condition tracker index*/,
                                       {ConditionState::kUnknown}, wizard, protoHash,
-                                      bucketStartTimeNs, bucketStartTimeNs);
+                                      bucketStartTimeNs, bucketStartTimeNs, provider);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 1);
@@ -268,9 +273,9 @@
     alert.set_trigger_if_sum_gt(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     sp<AnomalyTracker> anomalyTracker =
             countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -337,9 +342,9 @@
     metric.set_split_bucket_for_app_upgrade(true);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     // Bucket is flushed yet.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -399,8 +404,9 @@
     alert.set_trigger_if_sum_gt(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     sp<AnomalyTracker> anomalyTracker =
             countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -467,9 +473,10 @@
     metric.set_bucket(ONE_MINUTE);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs,
+                                      provider);
 
     sp<AnomalyTracker> anomalyTracker =
             countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -531,8 +538,9 @@
     int64_t oneDayNs = 24 * 60 * 60 * 1e9;
     int64_t fiveWeeksNs = 5 * 7 * oneDayNs;
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* meaning no condition */, {}, wizard,
-                                      protoHash, oneDayNs, fiveWeeksNs);
+                                      protoHash, oneDayNs, fiveWeeksNs, provider);
 
     int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs;
 
diff --git a/statsd/tests/metrics/DurationMetricProducer_test.cpp b/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 3b24d85..5cf0221 100644
--- a/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -71,11 +71,12 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2);
+            wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, provider);
 
     EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, durationProducer.mCurrentBucketNum);
@@ -99,11 +100,12 @@
     makeLogEvent(&event2, bucketStartTimeNs + bucketSizeNs + 2, tagId);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
@@ -142,12 +144,13 @@
     makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
             -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
             3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
-            bucketStartTimeNs, bucketStartTimeNs);
+            bucketStartTimeNs, bucketStartTimeNs, provider);
     durationProducer.mCondition = ConditionState::kFalse;
 
     assertConditionTimer(durationProducer.mConditionTimer, false, 0, 0);
@@ -200,12 +203,13 @@
     makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
             -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
             3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
-            bucketStartTimeNs, bucketStartTimeNs);
+            bucketStartTimeNs, bucketStartTimeNs, provider);
 
     EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition);
     EXPECT_FALSE(durationProducer.isConditionSliced());
@@ -248,11 +252,12 @@
     metric.set_split_bucket_for_app_upgrade(true);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -312,11 +317,12 @@
     metric.set_split_bucket_for_app_upgrade(true);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -377,11 +383,12 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     sp<AnomalyTracker> anomalyTracker =
             durationProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -425,11 +432,12 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -480,11 +488,12 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -542,11 +551,12 @@
     metric.set_split_bucket_for_app_upgrade(false);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -585,6 +595,7 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeLogEvent(&event1, bucketStartTimeNs + 50, tagId);
@@ -599,7 +610,7 @@
             kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
             -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
             3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
-            bucketStartTimeNs, bucketStartTimeNs);
+            bucketStartTimeNs, bucketStartTimeNs, provider);
 
     durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + 5);
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
diff --git a/statsd/tests/metrics/EventMetricProducer_test.cpp b/statsd/tests/metrics/EventMetricProducer_test.cpp
index d5fe932..a789f3b 100644
--- a/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -53,6 +53,7 @@
 
     parseStatsEventToLogEvent(statsEvent, logEvent);
 }
+
 }  // anonymous namespace
 
 class EventMetricProducerTest : public ::testing::Test {
@@ -80,9 +81,10 @@
     CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -117,10 +119,11 @@
     CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/,
                                       {ConditionState::kUnknown}, wizard, protoHash,
-                                      bucketStartTimeNs);
+                                      bucketStartTimeNs, provider);
 
     eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
@@ -174,10 +177,11 @@
     EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse));
     // Condition is true for second event.
     EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue));
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/,
                                       {ConditionState::kUnknown}, wizard, protoHash,
-                                      bucketStartTimeNs);
+                                      bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -213,8 +217,9 @@
     makeLogEvent(&event4, tagId, bucketStartTimeNs + 40, "222");
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -265,8 +270,9 @@
     makeLogEvent(&event4, tagId, bucketStartTimeNs + 40, "111", &bytesField2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -314,8 +320,9 @@
     makeLogEvent(&event3, tagId2, bucketStartTimeNs + 40, "222");
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -343,6 +350,107 @@
         }
     }
 }
+
+TEST_F(EventMetricProducerTest, TestCorruptedDataReasonSocketLoss_OnDumpReport) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int tagId = 1;
+
+    EventMetric metric;
+    metric.set_id(1);
+
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, tagId, bucketStartTimeNs + 10, "111");
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, protoHash, bucketStartTimeNs, provider);
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
+
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+
+    eventProducer.onMatchedLogEventLost(tagId, DATA_CORRUPTED_SOCKET_LOSS);
+
+    EXPECT_TRUE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+
+    // Check dump report content.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    eventProducer.onDumpReport(bucketStartTimeNs + 50, true /*include current partial bucket*/,
+                               true /*erase data*/, FAST, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    EXPECT_TRUE(report.has_event_metrics());
+    ASSERT_EQ(1, report.event_metrics().data_size());
+    ASSERT_EQ(1, report.data_corrupted_reason_size());
+    ASSERT_EQ(DATA_CORRUPTED_SOCKET_LOSS, report.data_corrupted_reason()[0]);
+
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+}
+
+TEST_F(EventMetricProducerTest, TestCorruptedDataReasonSocketLoss_OnDropData) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int tagId = 1;
+
+    EventMetric metric;
+    metric.set_id(1);
+
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, tagId, bucketStartTimeNs + 10, "111");
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, protoHash, bucketStartTimeNs, provider);
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
+
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+
+    eventProducer.onMatchedLogEventLost(tagId, DATA_CORRUPTED_SOCKET_LOSS);
+
+    EXPECT_TRUE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+
+    eventProducer.dropData(bucketStartTimeNs + 100);
+
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+}
+
+TEST_F(EventMetricProducerTest, TestCorruptedDataReasonSocketLoss_OnClearPastBuckets) {
+    int64_t bucketStartTimeNs = 10000000000;
+    int tagId = 1;
+
+    EventMetric metric;
+    metric.set_id(1);
+
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, tagId, bucketStartTimeNs + 10, "111");
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
+                                      wizard, protoHash, bucketStartTimeNs, provider);
+    eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
+
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+
+    eventProducer.onMatchedLogEventLost(tagId, DATA_CORRUPTED_SOCKET_LOSS);
+
+    EXPECT_TRUE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+
+    eventProducer.clearPastBuckets(bucketStartTimeNs + 100);
+
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToSocketLoss);
+    EXPECT_FALSE(eventProducer.mDataCorruptedDueToQueueOverflow);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index aa8af8f..ff3d8d1 100644
--- a/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -98,13 +98,14 @@
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     // statsd started long ago.
     // The metric starts in the middle of the bucket
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       -1, -1, tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs);
@@ -140,10 +141,12 @@
                 return true;
             }));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
@@ -228,10 +231,12 @@
     sp<EventMatcherWizard> eventMatcherWizard =
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       -1 /* -1 means no pulling */, -1, tagId, bucketStartTimeNs,
-                                      bucketStartTimeNs, pullerManager);
+                                      bucketStartTimeNs, pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     sp<AnomalyTracker> anomalyTracker =
@@ -310,6 +315,8 @@
     sp<EventMatcherWizard> eventMatcherWizard =
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
@@ -326,7 +333,7 @@
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
@@ -392,10 +399,12 @@
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _))
             .WillOnce(Return(false));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
@@ -446,10 +455,12 @@
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/,
-                                      {ConditionState::kUnknown}, wizard, protoHash,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
+    GaugeMetricProducer gaugeProducer(
+            kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard,
+            protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
+            bucketStartTimeNs, bucketStartTimeNs, pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     gaugeProducer.onConditionChanged(true, conditionChangeNs);
@@ -534,10 +545,12 @@
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/,
-                                      {ConditionState::kUnknown}, wizard, protoHash,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
+    GaugeMetricProducer gaugeProducer(
+            kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard,
+            protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
+            bucketStartTimeNs, bucketStartTimeNs, pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     gaugeProducer.onSlicedConditionMayChange(true, sliceConditionChangeNs);
@@ -579,10 +592,12 @@
     sp<EventMatcherWizard> eventMatcherWizard =
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     Alert alert;
@@ -679,10 +694,12 @@
             .WillOnce(Return(true));
 
     int triggerId = 5;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
@@ -737,10 +754,12 @@
                 return true;
             }));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, /*triggerId=*/-1, tagId, bucketStartTimeNs,
-                                      bucketStartTimeNs, pullerManager);
+                                      bucketStartTimeNs, pullerManager, provider);
 
     EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
     gaugeProducer.prepareFirstBucket();
@@ -808,10 +827,12 @@
             .WillOnce(Return(true));
 
     int triggerId = 5;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
@@ -878,10 +899,12 @@
             }));
 
     int triggerId = 5;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
@@ -950,11 +973,11 @@
                 data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 20, 1003, 18, 10));
                 return true;
             }));
-
-    GaugeMetricProducer gaugeProducer(kConfigKey, sampledGaugeMetric,
-                                      -1 /*-1 meaning no condition*/, {}, wizard, protoHash,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, triggerId,
-                                      tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    GaugeMetricProducer gaugeProducer(
+            kConfigKey, sampledGaugeMetric, -1 /*-1 meaning no condition*/, {}, wizard, protoHash,
+            logEventMatcherIndex, eventMatcherWizard, tagId, triggerId, tagId, bucketStartTimeNs,
+            bucketStartTimeNs, pullerManager, provider);
     SamplingInfo samplingInfo;
     samplingInfo.shardCount = shardCount;
     translateFieldMatcher(sampledGaugeMetric.dimensional_sampling_info().sampled_what_field(),
diff --git a/statsd/tests/metrics/KllMetricProducer_test.cpp b/statsd/tests/metrics/KllMetricProducer_test.cpp
index 0e2d4c2..58d8a73 100644
--- a/statsd/tests/metrics/KllMetricProducer_test.cpp
+++ b/statsd/tests/metrics/KllMetricProducer_test.cpp
@@ -137,6 +137,7 @@
             initialConditionCache.push_back(initialCondition.value());
         }
 
+        sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
         return new KllMetricProducer(
                 kConfigKey, metric, protoHash, {/*pullAtomId=*/-1, /*pullerManager=*/nullptr},
                 {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
@@ -147,7 +148,7 @@
                 {conditionIndex, metric.links(), initialConditionCache, wizard},
                 {metric.state_link(), slicedStateAtoms, stateGroupMap},
                 {/*eventActivationMap=*/{}, /*eventDeactivationMap=*/{}},
-                {dimensionSoftLimit, dimensionHardLimit});
+                {dimensionSoftLimit, dimensionHardLimit}, provider);
     }
 
     static KllMetric createMetric() {
diff --git a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
index 2381f04..972dc85 100644
--- a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
+++ b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
@@ -215,17 +215,27 @@
                         ? optional<int64_t>(metric.condition_correction_threshold_nanos())
                         : nullopt;
 
+        std::vector<ValueMetric::AggregationType> aggregationTypes;
+        if (metric.aggregation_types_size() != 0) {
+            for (int i = 0; i < metric.aggregation_types_size(); i++) {
+                aggregationTypes.push_back(metric.aggregation_types(i));
+            }
+        } else {  // aggregation_type() is set or default is used.
+            aggregationTypes.push_back(metric.aggregation_type());
+        }
+
+        sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
         sp<NumericValueMetricProducer> valueProducer = new NumericValueMetricProducer(
                 kConfigKey, metric, protoHash, {pullAtomId, pullerManager},
                 {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
                  conditionCorrectionThresholdNs, metric.split_bucket_for_app_upgrade()},
                 {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions,
                  logEventMatcherIndex, eventMatcherWizard, metric.dimensions_in_what(),
-                 fieldMatchers},
+                 fieldMatchers, aggregationTypes},
                 {conditionIndex, metric.links(), initialConditionCache, wizard},
                 {metric.state_link(), slicedStateAtoms, stateGroupMap},
                 {/*eventActivationMap=*/{}, /*eventDeactivationMap=*/{}},
-                {dimensionSoftLimit, dimensionHardLimit});
+                {dimensionSoftLimit, dimensionHardLimit}, provider);
 
         valueProducer->prepareFirstBucket();
         if (conditionAfterFirstBucketPrepared) {
@@ -7734,6 +7744,266 @@
                         0);  // Diff of 15 and 18
 }
 
+TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPulled) {
+    ValueMetric metric = NumericValueMetricProducerTestHelper::createMetricWithCondition();
+    // createMetricWithCondition() adds field 2 as first value field.
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(1);
+    metric.add_aggregation_types(ValueMetric::MIN);
+    metric.add_aggregation_types(ValueMetric::MAX);
+    metric.add_aggregation_types(ValueMetric::SUM);
+    metric.add_aggregation_types(ValueMetric::AVG);
+    metric.add_aggregation_types(ValueMetric::SUM);
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+            // Screen On Pull 1.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 1, 2));
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 2, 4));
+                return true;
+            }))
+            // Screen Off Pull 2.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 3, 5));
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 4, 9));
+                return true;
+            }))
+            // Screen On Pull 3.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 5, 10));
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 6, 20));
+                return true;
+            }))
+            // Dump report pull.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 55 * NS_PER_SEC,
+                                                       25, 60));
+                data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 55 * NS_PER_SEC,
+                                                       35, 80));
+
+                return true;
+            }));
+
+    sp<NumericValueMetricProducer> valueProducer =
+            NumericValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kFalse);
+
+    EXPECT_EQ(5, valueProducer->mFieldMatchers.size());
+    ASSERT_EQ(5, valueProducer->mAggregationTypes.size());
+    EXPECT_EQ(ValueMetric::MIN, valueProducer->mAggregationTypes[0]);
+    EXPECT_EQ(ValueMetric::MAX, valueProducer->mAggregationTypes[1]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[2]);
+    EXPECT_EQ(ValueMetric::AVG, valueProducer->mAggregationTypes[3]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[4]);
+
+    // Screen On. Pull 1.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 30 * NS_PER_SEC);
+
+    // Screen Off.
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 40 * NS_PER_SEC);
+
+    // Screen On. Pull 2.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 50 * NS_PER_SEC);
+
+    // Bucket 2 start. Pull 4.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 15, 30));
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 20, 40));
+    valueProducer->onDataPulled(allData, PullResult::PULL_RESULT_SUCCESS, bucket2StartTimeNs);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    backfillDimensionPath(&report);
+    backfillStartEndTimestamp(&report);
+    EXPECT_TRUE(report.has_value_metrics());
+    StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    sortMetricDataByDimensionsValue(report.value_metrics(), &valueMetrics);
+    ASSERT_EQ(1, valueMetrics.data_size());
+    EXPECT_EQ(0, report.value_metrics().skipped_size());
+
+    // Bucket 1.
+    // Value field 1
+    // Diff from pulls 1 and 2: (3+4)-(1+2) = 4
+    // Diff from pulls 3 and 4: (15+20)-(5+6) = 24
+
+    // Value field 2
+    // Diff from pulls 1 and 2: (5+9)-(2+4) = 8
+    // Diff from pulls 3 and 4: (30+40)-(10+20) = 40
+
+    // Bucket 2
+    // Value field 1
+    // Diff from pulls 4 and 5: (25+35)-(15+20) = 25
+
+    // Value field 2
+    // Diff from pulls 4 and 5: (60+80)-(30+40) = 70
+
+    // Output values are calculated for these agg type - value field combinations
+    // MIN-2, MAX-2, SUM-2, AVG-2, SUM-1
+    ValueMetricData data = valueMetrics.data(0);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs,
+                        {8, 40, 48, 24, 28}, 20 * NS_PER_SEC, 0);
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs,
+                        {70, 70, 70, 70, 25}, 55 * NS_PER_SEC, 0);
+}
+
+TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPushed) {
+    ValueMetric metric = NumericValueMetricProducerTestHelper::createMetric();
+    metric.mutable_dimensions_in_what()->set_field(tagId);
+    metric.mutable_dimensions_in_what()->add_child()->set_field(1);
+    // createMetric() adds field 2 as first value field.
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(3);
+    metric.add_aggregation_types(ValueMetric::MIN);
+    metric.add_aggregation_types(ValueMetric::MAX);
+    metric.add_aggregation_types(ValueMetric::SUM);
+    metric.add_aggregation_types(ValueMetric::AVG);
+    metric.add_aggregation_types(ValueMetric::SUM);
+
+    sp<EventMatcherWizard> eventMatcherWizard =
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    sp<NumericValueMetricProducer> valueProducer =
+            NumericValueMetricProducerTestHelper::createValueProducerNoConditions(
+                    pullerManager, metric, /*pullAtomId=*/-1);
+
+    EXPECT_EQ(5, valueProducer->mFieldMatchers.size());
+    ASSERT_EQ(5, valueProducer->mAggregationTypes.size());
+    EXPECT_EQ(ValueMetric::MIN, valueProducer->mAggregationTypes[0]);
+    EXPECT_EQ(ValueMetric::MAX, valueProducer->mAggregationTypes[1]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[2]);
+    EXPECT_EQ(ValueMetric::AVG, valueProducer->mAggregationTypes[3]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[4]);
+
+    // Bucket 1 events.
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 5, 10);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 6, 8);
+
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event3, tagId, bucketStartTimeNs + 40, 2, 3, 10);
+
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event4, tagId, bucketStartTimeNs + 50, 2, 4, 6);
+
+    LogEvent event5(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event5, tagId, bucketStartTimeNs + 30, 1, 19, 9);
+
+    LogEvent event6(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event6, tagId, bucketStartTimeNs + 60, 2, 20, 8);
+
+    // Bucket 2 events.
+    LogEvent event7(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event7, tagId, bucket2StartTimeNs + 10, 2, 7, 41);
+
+    LogEvent event8(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event8, tagId, bucket2StartTimeNs + 20, 1, 21, 40);
+
+    LogEvent event9(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event9, tagId, bucket2StartTimeNs + 30, 1, 10, 4);
+
+    LogEvent event10(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event10, tagId, bucket2StartTimeNs + 40, 2, 3, 50);
+
+    LogEvent event11(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event11, tagId, bucket2StartTimeNs + 50, 1, 20, 7);
+
+    LogEvent event12(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event12, tagId, bucket2StartTimeNs + 60, 2, 20, 2);
+
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event1);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event2);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event3);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event4);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event5);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event6);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event7);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event8);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event9);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event10);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event11);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event12);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    valueProducer->onDumpReport(bucket3StartTimeNs + 10000, false /* include recent buckets */,
+                                true, FAST /* dumpLatency */, nullptr, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    backfillDimensionPath(&report);
+    backfillStartEndTimestamp(&report);
+    EXPECT_TRUE(report.has_value_metrics());
+    StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    sortMetricDataByDimensionsValue(report.value_metrics(), &valueMetrics);
+    ASSERT_EQ(2, valueMetrics.data_size());
+    EXPECT_EQ(0, report.value_metrics().skipped_size());
+
+    // Bucket 1.
+    // Value field 2
+    // dim 1 pushed values: 5, 6, 19
+    // dim 2 pushed values: 3, 4, 20
+
+    // Value field 3
+    // dim 1 pushed values: 10, 8, 9
+    // dim 2 pushed values: 10, 6, 8
+
+    // Bucket 2
+    // Value field 2
+    // dim 1 pushed values: 21, 10, 20
+    // dim 2 pushed values: 7, 3, 20
+
+    // Value field 3
+    // dim 1 pushed values: 40, 4, 7
+    // dim 2 pushed values: 41, 50, 2
+
+    // Output values are calculated for these agg type - value field combinations
+    // MIN-2, MAX-2, SUM-2, AVG-2, SUM-1
+    ValueMetricData data = valueMetrics.data(0);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs,
+                        {5, 19, 30, 10, 27}, 0, 0);
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, bucket3StartTimeNs,
+                        {10, 21, 51, 17, 51}, 0, 0);
+
+    data = valueMetrics.data(1);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs,
+                        {3, 20, 27, 9, 24}, 0, 0);
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, bucket3StartTimeNs,
+                        {3, 20, 30, 10, 93}, 0, 0);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
index 44b812f..2085e11 100644
--- a/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
+++ b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
@@ -54,11 +54,12 @@
 TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventMultipleEvents) {
     EventMetric metric;
     metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     RestrictedEventMetricProducer producer(configKey, metric,
                                            /*conditionIndex=*/-1,
                                            /*initialConditionCache=*/{}, new ConditionWizard(),
                                            /*protoHash=*/0x1234567890,
-                                           /*startTimeNs=*/0);
+                                           /*startTimeNs=*/0, provider);
     std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
     std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
 
@@ -87,11 +88,12 @@
 TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventMultipleFields) {
     EventMetric metric;
     metric.set_id(metricId2);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     RestrictedEventMetricProducer producer(configKey, metric,
                                            /*conditionIndex=*/-1,
                                            /*initialConditionCache=*/{}, new ConditionWizard(),
                                            /*protoHash=*/0x1234567890,
-                                           /*startTimeNs=*/0);
+                                           /*startTimeNs=*/0, provider);
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, 1);
     AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
@@ -129,12 +131,13 @@
     EventMetric metric;
     metric.set_id(metricId1);
     metric.set_condition(StringToId("SCREEN_ON"));
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     RestrictedEventMetricProducer producer(configKey, metric,
                                            /*conditionIndex=*/0,
                                            /*initialConditionCache=*/{ConditionState::kUnknown},
                                            new ConditionWizard(),
                                            /*protoHash=*/0x1234567890,
-                                           /*startTimeNs=*/0);
+                                           /*startTimeNs=*/0, provider);
     std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
     std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
 
@@ -162,11 +165,12 @@
 TEST_F(RestrictedEventMetricProducerTest, TestOnDumpReportNoOp) {
     EventMetric metric;
     metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     RestrictedEventMetricProducer producer(configKey, metric,
                                            /*conditionIndex=*/-1,
                                            /*initialConditionCache=*/{}, new ConditionWizard(),
                                            /*protoHash=*/0x1234567890,
-                                           /*startTimeNs=*/0);
+                                           /*startTimeNs=*/0, provider);
     std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*timestampNs=*/1);
     producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
     ProtoOutputStream output;
@@ -182,11 +186,12 @@
 TEST_F(RestrictedEventMetricProducerTest, TestOnMetricRemove) {
     EventMetric metric;
     metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     RestrictedEventMetricProducer producer(configKey, metric,
                                            /*conditionIndex=*/-1,
                                            /*initialConditionCache=*/{}, new ConditionWizard(),
                                            /*protoHash=*/0x1234567890,
-                                           /*startTimeNs=*/0);
+                                           /*startTimeNs=*/0, provider);
     EXPECT_FALSE(metricTableExist(metricId1));
 
     std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*timestampNs=*/1);
@@ -201,11 +206,12 @@
 TEST_F(RestrictedEventMetricProducerTest, TestRestrictedEventMetricTtlDeletesFirstEvent) {
     EventMetric metric;
     metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     RestrictedEventMetricProducer producer(configKey, metric,
                                            /*conditionIndex=*/-1,
                                            /*initialConditionCache=*/{}, new ConditionWizard(),
                                            /*protoHash=*/0x1234567890,
-                                           /*startTimeNs=*/0);
+                                           /*startTimeNs=*/0, provider);
 
     int64_t currentTimeNs = getWallClockNs();
     int64_t eightDaysAgo = currentTimeNs - 8 * 24 * 3600 * NS_PER_SEC;
@@ -243,11 +249,12 @@
     metricMetadata.set_restricted_category(1);  // CATEGORY_DIAGNOSTIC
     EventMetric metric;
     metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     RestrictedEventMetricProducer producer(configKey, metric,
                                            /*conditionIndex=*/-1,
                                            /*initialConditionCache=*/{}, new ConditionWizard(),
                                            /*protoHash=*/0x1234567890,
-                                           /*startTimeNs=*/0);
+                                           /*startTimeNs=*/0, provider);
 
     producer.loadMetricMetadataFromProto(metricMetadata);
 
diff --git a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index b32d5d3..0861812 100644
--- a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -64,6 +64,7 @@
         /*minDiffToUpdateRegisteredAlarmTimeSec=*/0,
         [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
         [](const shared_ptr<IStatsCompanionService>&) {});
+sp<ConfigMetadataProvider> configMetadataProvider;
 unordered_map<int, vector<int>> allTagIdsToMatchersMap;
 vector<sp<AtomMatchingTracker>> oldAtomMatchingTrackers;
 unordered_map<int64_t, int> oldAtomMatchingTrackerMap;
@@ -87,10 +88,11 @@
     // initStatsdConfig returns nullopt if config is valid
     return !initStatsdConfig(
                     key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-                    timeBaseNs, timeBaseNs, allTagIdsToMatchersMap, oldAtomMatchingTrackers,
-                    oldAtomMatchingTrackerMap, oldConditionTrackers, oldConditionTrackerMap,
-                    oldMetricProducers, oldMetricProducerMap, oldAnomalyTrackers, oldAlarmTrackers,
-                    tmpConditionToMetricMap, tmpTrackerToMetricMap, tmpTrackerToConditionMap,
+                    timeBaseNs, timeBaseNs, configMetadataProvider, allTagIdsToMatchersMap,
+                    oldAtomMatchingTrackers, oldAtomMatchingTrackerMap, oldConditionTrackers,
+                    oldConditionTrackerMap, oldMetricProducers, oldMetricProducerMap,
+                    oldAnomalyTrackers, oldAlarmTrackers, tmpConditionToMetricMap,
+                    tmpTrackerToMetricMap, tmpTrackerToConditionMap,
                     tmpActivationAtomTrackerToMetricMap, tmpDeactivationAtomTrackerToMetricMap,
                     oldAlertTrackerMap, metricsWithActivation, oldStateHashes, noReportMetricIds)
                     .has_value();
@@ -1974,16 +1976,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     unordered_map<int64_t, int> expectedMetricProducerMap = {
@@ -2207,14 +2210,15 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, /*replacedConditions=*/{}, newConditionTrackers,
                             conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
-                            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
-                            newMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                            noReportMetricIds, activationAtomTrackerToMetricMap,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
                             replacedMetrics),
               nullopt);
@@ -2420,16 +2424,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, /*replacedConditions=*/{}, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     unordered_map<int64_t, int> expectedMetricProducerMap = {
@@ -2743,12 +2748,13 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
                             newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
                             newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps,
-                            replacedStates, oldMetricProducerMap, oldMetricProducers,
+                            replacedStates, oldMetricProducerMap, oldMetricProducers, provider,
                             newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                             trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
@@ -3011,12 +3017,13 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
                             newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
                             newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps,
-                            replacedStates, oldMetricProducerMap, oldMetricProducers,
+                            replacedStates, oldMetricProducerMap, oldMetricProducers, provider,
                             newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                             trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
@@ -3228,13 +3235,14 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(
                       key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                       new StatsPullerManager(), oldAtomMatchingTrackerMap,
                       newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, newAtomMatchingTrackers,
                       newConditionTrackerMap, replacedConditions, newConditionTrackers,
                       conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
-                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
+                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, provider,
                       newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                       trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                       deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
@@ -3401,16 +3409,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     // Verify event activation/deactivation maps.
@@ -3563,16 +3572,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, /*replacedConditions=*/{}, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap*/ {}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     unordered_map<int64_t, int> expectedMetricProducerMap = {
@@ -3794,13 +3804,14 @@
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
     int64_t currentTimeNs = 12345;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(
                       key, config, /*timeBaseNs=*/123, currentTimeNs, new StatsPullerManager(),
                       oldAtomMatchingTrackerMap, oldAtomMatchingTrackerMap, /*replacedMatchers*/ {},
                       oldAtomMatchingTrackers, oldConditionTrackerMap, /*replacedConditions=*/{},
                       oldConditionTrackers, {ConditionState::kUnknown}, /*stateAtomIdMap*/ {},
                       /*allStateGroupMaps=*/{},
-                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
+                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, provider,
                       newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                       trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                       deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
@@ -3983,14 +3994,15 @@
     set<int64_t> replacedMatchers;
     set<int64_t> replacedConditions;
     set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
-                            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
-                            newMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                            noReportMetricIds, activationAtomTrackerToMetricMap,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
                             replacedMetrics),
               InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_HAS_MULTIPLE_ACTIVATIONS, metricId));
@@ -4020,14 +4032,15 @@
     set<int64_t> replacedMatchers;
     set<int64_t> replacedConditions;
     set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
-                            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
-                            newMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                            noReportMetricIds, activationAtomTrackerToMetricMap,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
                             replacedMetrics),
               InvalidConfigReason(INVALID_CONFIG_REASON_NO_REPORT_METRIC_NOT_FOUND, metricId));
@@ -4064,6 +4077,7 @@
     set<int64_t> replacedMatchers;
     set<int64_t> replacedConditions;
     set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     newAtomMatchingTrackerMap[StringToId("ScreenTurnedOn")] = 0;
     stateAtomIdMap[StringToId("ScreenState")] = util::SCREEN_STATE_CHANGED;
@@ -4074,7 +4088,7 @@
                     replacedMatchers, newAtomMatchingTrackers, newConditionTrackerMap,
                     replacedConditions, newConditionTrackers, conditionCache, stateAtomIdMap,
                     allStateGroupMaps, replacedStates, oldMetricProducerMap, oldMetricProducers,
-                    newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                    provider, newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                     trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                     deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
             InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SLICED_STATE_ATOM_ALLOWED_FROM_ANY_UID,
@@ -4145,15 +4159,16 @@
     set<int64_t> replacedMatchers;
     set<int64_t> replacedConditions;
     set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
-                            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
-                            newMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                            noReportMetricIds, activationAtomTrackerToMetricMap,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
                             replacedMetrics),
               InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
@@ -4196,13 +4211,14 @@
     vector<int> metricsWithActivation;
     vector<sp<MetricProducer>> newMetricProducers;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(
                       key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                       new StatsPullerManager(), oldAtomMatchingTrackerMap,
                       oldAtomMatchingTrackerMap, /*replacedMatchers=*/{}, oldAtomMatchingTrackers,
                       oldConditionTrackerMap, /*replacedConditions=*/{}, oldConditionTrackers,
                       /*conditionCache=*/{}, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
-                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
+                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, provider,
                       newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                       trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                       deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
diff --git a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
index da678b4..1a64c20 100644
--- a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -60,6 +60,7 @@
 sp<StatsPullerManager> pullerManager = new StatsPullerManager();
 sp<AlarmMonitor> anomalyAlarmMonitor;
 sp<AlarmMonitor> periodicAlarmMonitor;
+sp<ConfigMetadataProvider> configMetadataProvider;
 unordered_map<int, vector<int>> allTagIdsToMatchersMap;
 vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
 unordered_map<int64_t, int> atomMatchingTrackerMap;
@@ -83,12 +84,12 @@
     // initStatsdConfig returns nullopt if config is valid
     return initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIdsToMatchersMap, allAtomMatchingTrackers,
-            atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap, allMetricProducers,
-            metricProducerMap, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-            trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap,
-            deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
-            stateProtoHashes, noReportMetricIds);
+            timeBaseSec, timeBaseSec, configMetadataProvider, allTagIdsToMatchersMap,
+            allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers,
+            conditionTrackerMap, allMetricProducers, metricProducerMap, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, stateProtoHashes, noReportMetricIds);
 }
 
 StatsdConfig buildCircleMatchers() {
@@ -707,6 +708,79 @@
                                   StringToId("Gauge")));
 }
 
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricInvalidPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(101);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricInvalidPullProbabilityZero) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(0);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricValidPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(50);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestPushedGaugeMetricWithPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("ScreenTurnedOn"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(50);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_GAUGE_METRIC_PUSHED_WITH_PULL_PROBABILITY,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricRandomOneSampleWithPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::RANDOM_ONE_SAMPLE,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(50);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(
+                      INVALID_CONFIG_REASON_GAUGE_METRIC_RANDOM_ONE_SAMPLE_WITH_PULL_PROBABILITY,
+                      StringToId("Gauge")));
+}
+
 TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMissingIdOrWhat) {
     StatsdConfig config;
     int64_t metricId = 1;
@@ -732,6 +806,88 @@
                                   StringToId("NumericValue")));
 }
 
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricHasBothSingleAndMultipleAggTypes) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    metric->set_aggregation_type(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(
+                      INVALID_CONFIG_REASON_VALUE_METRIC_DEFINES_SINGLE_AND_MULTIPLE_AGG_TYPES,
+                      StringToId("NumericValue")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMoreAggTypesThanValueFields) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+
+    EXPECT_EQ(
+            initConfig(config),
+            InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE,
+                                StringToId("NumericValue")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMoreValueFieldsThanAggTypes) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    // This only fails if the repeated aggregation field is used. If the single field is used,
+    // we will apply this aggregation type to all value fields.
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+    *metric->mutable_value_field() = CreateDimensions(
+            util::SUBSYSTEM_SLEEP_STATE, {3 /* count */, 4 /* time_millis */, 3 /* count */});
+
+    EXPECT_EQ(
+            initConfig(config),
+            InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE,
+                                StringToId("NumericValue")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricDefaultAggTypeOutOfOrderFields) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    *metric->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time_millis */, 3 /* count */});
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMultipleAggTypesOutOfOrderFields) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+    metric->add_aggregation_types(ValueMetric::SUM);
+    *metric->mutable_value_field() = CreateDimensions(
+            util::SUBSYSTEM_SLEEP_STATE, {3 /* count */, 4 /* time_millis */, 3 /* count */});
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
 TEST_F(MetricsManagerUtilTest, TestKllMetricMissingIdOrWhat) {
     StatsdConfig config;
     int64_t metricId = 1;
@@ -1383,8 +1539,10 @@
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
-            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    vector<sp<MetricProducer>> metricProducers(
+            {new CountMetricProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                     0x0123456789, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
@@ -1406,8 +1564,10 @@
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
-            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    vector<sp<MetricProducer>> metricProducers(
+            {new CountMetricProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                     0x0123456789, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
@@ -1430,8 +1590,10 @@
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
-            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    vector<sp<MetricProducer>> metricProducers(
+            {new CountMetricProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                     0x0123456789, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_NE(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
@@ -1455,10 +1617,11 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
     FieldMatcher dimensions;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     vector<sp<MetricProducer>> metricProducers({new DurationMetricProducer(
             kConfigKey, metric, -1 /*no condition*/, {}, -1 /* what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, 0x0123456789, dimensions, 0, 0)});
+            wizard, 0x0123456789, dimensions, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp
index 8e80a20..539c890 100644
--- a/statsd/tests/statsd_test_util.cpp
+++ b/statsd/tests/statsd_test_util.cpp
@@ -2314,6 +2314,13 @@
 
     return config;
 }
+
+sp<MockConfigMetadataProvider> makeMockConfigMetadataProvider(bool enabled) {
+    sp<MockConfigMetadataProvider> metadataProvider = new StrictMock<MockConfigMetadataProvider>();
+    EXPECT_CALL(*metadataProvider, useV2SoftMemoryCalculation()).Times(AnyNumber());
+    EXPECT_CALL(*metadataProvider, useV2SoftMemoryCalculation()).WillRepeatedly(Return(enabled));
+    return nullptr;
+}
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h
index d2d10b2..8fe6440 100644
--- a/statsd/tests/statsd_test_util.h
+++ b/statsd/tests/statsd_test_util.h
@@ -837,6 +837,13 @@
 
 StatsdConfig buildGoodConfig(int configId, int alertId);
 
+class MockConfigMetadataProvider : public ConfigMetadataProvider {
+public:
+    MOCK_METHOD(bool, useV2SoftMemoryCalculation, (), (override));
+};
+
+sp<MockConfigMetadataProvider> makeMockConfigMetadataProvider(bool enabled);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java
index 0061cd4..489288e 100644
--- a/statsd/tools/localtools/src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java
@@ -22,6 +22,7 @@
 import com.android.os.adservices.AdservicesExtensionAtoms;
 import com.android.os.art.ArtExtensionAtoms;
 import com.android.os.automotive.caruilib.AutomotiveCaruilibAtoms;
+import com.android.os.bluetooth.BluetoothExtensionAtoms;
 import com.android.os.devicelogs.DeviceLogsAtoms;
 import com.android.os.dnd.DndAtoms;
 import com.android.os.dnd.DndExtensionAtoms;
@@ -61,6 +62,7 @@
 import android.os.statsd.media.MediaCodecExtensionAtoms;
 import com.android.os.credentials.CredentialsExtensionAtoms;
 import com.android.os.sdksandbox.SdksandboxExtensionAtoms;
+import com.android.os.apex.ApexExtensionAtoms;
 
 import com.google.protobuf.ExtensionRegistry;
 
@@ -96,6 +98,7 @@
         ShellConfig.registerAllExtensions(extensionRegistry);
         AdservicesExtensionAtoms.registerAllExtensions(extensionRegistry);
         AutomotiveCaruilibAtoms.registerAllExtensions(extensionRegistry);
+        BluetoothExtensionAtoms.registerAllExtensions(extensionRegistry);
         DeviceLogsAtoms.registerAllExtensions(extensionRegistry);
         DndAtoms.registerAllExtensions(extensionRegistry);
         DndExtensionAtoms.registerAllExtensions(extensionRegistry);
@@ -136,5 +139,6 @@
         CredentialsExtensionAtoms.registerAllExtensions(extensionRegistry);
         SdksandboxExtensionAtoms.registerAllExtensions(extensionRegistry);
         ArtExtensionAtoms.registerAllExtensions(extensionRegistry);
+        ApexExtensionAtoms.registerAllExtensions(extensionRegistry);
     }
 }
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 448536e..05b7fb5 100644
--- a/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -99,6 +99,9 @@
             "com.google.android.healthconnect.controller",
             "com.android.telephony.qns",
             "com.android.car",
+            "com.android.ondevicepersonalization.services",
+            "com.google.android.ondevicepersonalization.services",
+            "AID_UPROBESTATS",
     };
     private static final String[] DEFAULT_PULL_SOURCES = {
             "AID_KEYSTORE", "AID_RADIO", "AID_SYSTEM",
@@ -155,7 +158,8 @@
         LOGGER.severe("-e");
         LOGGER.severe("\tWait for Enter key press before collecting report");
         LOGGER.severe("-d delay_ms");
-        LOGGER.severe("\tWait for delay_ms before collecting report, default is 60000 ms");
+        LOGGER.severe("\tWait for delay_ms before collecting report, default is 60000 ms. Only");
+        LOGGER.severe("\taffects collection of pushed atoms.");
         LOGGER.severe("-v");
         LOGGER.severe("\tDebug logging level");
     }
diff --git a/tests/Android.bp b/tests/Android.bp
index e67a76c..71784a9 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -42,6 +42,7 @@
     static_libs: [
         "core_cts_test_resources",
         "perfetto_config-full",
+        "cts-statsd-atom-host-test-utils",
     ],
     data: [
         "**/*.pbtxt",
diff --git a/tests/src/android/cts/statsd/alarm/AlarmTests.java b/tests/src/android/cts/statsd/alarm/AlarmTests.java
index 032297e..0f49e9a 100644
--- a/tests/src/android/cts/statsd/alarm/AlarmTests.java
+++ b/tests/src/android/cts/statsd/alarm/AlarmTests.java
@@ -17,21 +17,29 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.AtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
-import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.Alarm;
 import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-import java.util.List;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 
 /**
  * Statsd Anomaly Detection tests.
  */
-public class AlarmTests extends AtomTestCase {
+public class AlarmTests extends DeviceTestCase implements IBuildReceiver {
 
     private static final String TAG = "Statsd.AnomalyDetectionTests";
 
@@ -42,36 +50,61 @@
     private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
     private static final int INCIDENTD_SECTION = -1;
 
+    private IBuildInfo mCtsBuild;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
         if (!INCIDENTD_TESTS_ENABLED) {
             CLog.w(TAG, TAG + " alarm tests are disabled by a flag. Change flag to true to run");
         }
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
     }
 
     public void testAlarm() throws Exception {
         StatsdConfig.Builder config = getBaseConfig();
-        turnScreenOn();
-        uploadConfig(config);
+        DeviceUtils.turnScreenOn(getDevice());
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        Thread.sleep(9_000);
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        RunUtil.getDefault().sleep(9_000);
 
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
 
     private final StatsdConfig.Builder getBaseConfig() throws Exception {
-      return createConfigBuilder()
-          .addAlarm(Alarm.newBuilder().setId(ALARM_ID).setOffsetMillis(2).setPeriodMillis(
-              5_000) // every 5 seconds.
-              )
-          .addSubscription(Subscription.newBuilder()
-                               .setId(SUBSCRIPTION_ID_INCIDENTD)
-                               .setRuleType(Subscription.RuleType.ALARM)
-                               .setRuleId(ALARM_ID)
-                               .setIncidentdDetails(
-                                   IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)));
+        return ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                .addAlarm(Alarm.newBuilder()
+                        .setId(ALARM_ID)
+                        .setOffsetMillis(2)
+                        .setPeriodMillis(5_000) // every 5 seconds.
+                )
+                .addSubscription(Subscription.newBuilder()
+                        .setId(SUBSCRIPTION_ID_INCIDENTD)
+                        .setRuleType(Subscription.RuleType.ALARM)
+                        .setRuleId(ALARM_ID)
+                        .setIncidentdDetails(IncidentdDetails.newBuilder()
+                                .addSection(INCIDENTD_SECTION)));
     }
 }
diff --git a/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java
index ea47cc3..74fe1b5 100644
--- a/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ b/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java
@@ -18,7 +18,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.AtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.Alert;
@@ -38,13 +42,30 @@
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.DebugElapsedClock;
 import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.protobuf.ByteString;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.List;
+import java.util.Random;
+
+import perfetto.protos.PerfettoConfig.DataSourceConfig;
+import perfetto.protos.PerfettoConfig.FtraceConfig;
+import perfetto.protos.PerfettoConfig.TraceConfig;
 
 /**
  * Statsd Anomaly Detection tests.
  */
-public class AnomalyDetectionTests extends AtomTestCase {
+public class AnomalyDetectionTests extends DeviceTestCase implements IBuildReceiver {
 
     private static final String TAG = "Statsd.AnomalyDetectionTests";
 
@@ -53,6 +74,8 @@
 
     private static final int WAIT_AFTER_BREADCRUMB_MS = 2000;
 
+    private static final int SHELL_UID = 2000;
+
     // Config constants
     private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
     private static final int APP_BREADCRUMB_REPORTED_MATCH_STOP_ID = 2;
@@ -66,14 +89,22 @@
 
     private boolean defaultSystemTracingConfigurationHasChanged = false;
 
+    private IBuildInfo mCtsBuild;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
         if (!INCIDENTD_TESTS_ENABLED) {
             CLog.w(TAG, TAG + " anomaly tests are disabled by a flag. Change flag to true to run");
         }
         if (PERFETTO_TESTS_ENABLED) {
-            // Default Android configuration can only change for device type that doesn't require SystemTracingEnabled
+            // Default Android configuration can only change for device type that doesn't require
+            // SystemTracingEnabled
             // by default in CDD.
             String chars = getDevice().getProperty("ro.build.characteristics");
             if (!isSystemTracingEnabled() && chars.contains("automotive")) {
@@ -81,11 +112,20 @@
                 defaultSystemTracingConfigurationHasChanged = true;
             }
         }
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
     }
 
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         if (PERFETTO_TESTS_ENABLED) {
             // Disable SystemTracing if previously enabled at test setUp()
             if (defaultSystemTracingConfigurationHasChanged) {
@@ -95,11 +135,12 @@
             final long deadLine = System.currentTimeMillis() + 10000;
             while (isSystemTracingEnabled()) {
                 if (System.currentTimeMillis() > deadLine) {
-                    CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : " + isSystemTracingEnabled());
+                    CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : "
+                            + isSystemTracingEnabled());
                     break;
                 }
                 CLog.d("Waiting to finish collecting traces. ");
-                Thread.sleep(WAIT_TIME_SHORT);
+                RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
             }
         }
     }
@@ -120,34 +161,50 @@
                                 )
                         )
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
         // count(label=6) -> 1 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(6);
-        Thread.sleep(500);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 6);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
         // count(label=6) -> 2 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(6);
-        Thread.sleep(500);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 6);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
         // count(label=12) -> 1 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(12);
-        Thread.sleep(1000);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 12);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
-        doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
     // Tests that anomaly detection for duration works.
@@ -169,49 +226,64 @@
                                         .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
                                 )
                         );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
         // Since timing is crucial and checking logcat for incidentd is slow, we don't test for it.
 
         // Test that alarm doesn't fire early.
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(6_000);  // Recorded duration at end: 6s
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(6_000);  // Recorded duration at end: 6s
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(4_000);  // Recorded duration at end: 6s
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(4_000);  // Recorded duration at end: 6s
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
         // Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(9_000);  // Recorded duration at end: 13s
-        doAppBreadcrumbReported(2);
-        List<EventMetricData> data = getEventMetricDataList();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(9_000);  // Recorded duration at end: 13s
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 2);
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
 
         // Now test that the refractory period is obeyed.
-        markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStop(1);
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(3_000);  // Recorded duration at end: 13s
+        markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(3_000);  // Recorded duration at end: 13s
         // NB: the previous getEventMetricDataList also removes the report, so size is back to 0.
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
         // Test that detection works again after refractory period finishes.
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(8_000);  // Recorded duration at end: 9s
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(15_000);  // Recorded duration at end: 15s
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(8_000);  // Recorded duration at end: 9s
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(15_000);  // Recorded duration at end: 15s
         // We can do an incidentd test now that all the timing issues are done.
-        doAppBreadcrumbReported(2);
-        data = getEventMetricDataList();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 2);
+        data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
 
-        doAppBreadcrumbReportedStop(1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
     }
 
     // Tests that anomaly detection for duration works even when the alarm fires too late.
@@ -233,24 +305,29 @@
                                         .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
                                 )
                         );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(5_000);
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(2_000);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(5_000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(2_000);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
         // Test that alarm does fire when it is supposed to.
         // The anomaly occurs in 1s, but alarms won't fire that quickly.
         // It is likely that the alarm will only fire after this period is already over, but the
         // anomaly should nonetheless be detected when the event stops.
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(1_200);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(1_200);
         // Anomaly should be detected here if the alarm didn't fire yet.
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(200);
-        List<EventMetricData> data = getEventMetricDataList();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(200);
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         if (data.size() == 2) {
             // Although we expect that the alarm won't fire, we certainly cannot demand that.
             CLog.w(TAG, "The anomaly was detected twice. Presumably the alarm did manage to fire.");
@@ -274,28 +351,37 @@
                         )
 
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // value = 6, which is NOT > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
-        doAppBreadcrumbReportedStart(14); // value = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                14); // value = 14 > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
     // Test that anomaly detection integrates with perfetto properly.
     public void testPerfetto() throws Exception {
         String chars = getDevice().getProperty("ro.build.characteristics");
         if (chars.contains("watch")) {
-                return;
+            return;
         }
 
         if (PERFETTO_TESTS_ENABLED) resetPerfettoGuardrails();
@@ -320,32 +406,38 @@
                         )
 
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // value = 6, which is NOT > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
         if (PERFETTO_TESTS_ENABLED) assertThat(isSystemTracingEnabled()).isFalse();
 
-        doAppBreadcrumbReportedStart(14); // value = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                14); // value = 14 > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
 
-        // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to happen.
+        // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to
+        // happen.
         if (PERFETTO_TESTS_ENABLED) {
-                boolean tracingEnabled = false;
-                for (int i = 0; i < 5; i++) {
-                        if (isSystemTracingEnabled()) {
-                                tracingEnabled = true;
-                                break;
-                        }
-                        Thread.sleep(1000);
+            boolean tracingEnabled = false;
+            for (int i = 0; i < 5; i++) {
+                if (isSystemTracingEnabled()) {
+                    tracingEnabled = true;
+                    break;
                 }
-                assertThat(tracingEnabled).isTrue();
+                RunUtil.getDefault().sleep(1000);
+            }
+            assertThat(tracingEnabled).isTrue();
         }
     }
 
@@ -365,22 +457,32 @@
                                 )
                         )
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
-        Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // gauge = 6, which is NOT > trigger
+        RunUtil.getDefault().sleep(
+                Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
         // We waited for >1s above, so we are now in the next bucket (which is essential).
-        doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                14); // gauge = 14 > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
     // Test that anomaly detection for pulled metrics work.
@@ -410,73 +512,165 @@
                                 )
                         )
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        Thread.sleep(6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
+        RunUtil.getDefault().sleep(
+                6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertThat(data.size()).isEqualTo(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
     }
 
 
     private final StatsdConfig.Builder getBaseConfig(int numBuckets,
-                                                     int refractorySecs,
-                                                     long triggerIfSumGt) throws Exception {
-      return createConfigBuilder()
-          // Items of relevance for detecting the anomaly:
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                          // Event only when the uid is this app's uid.
-                          .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(
-                              createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                  .setEqInt(AppBreadcrumbReported.State.START.ordinal()))))
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                          // Event only when the uid is this app's uid.
-                          .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(
-                              createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                  .setEqInt(AppBreadcrumbReported.State.STOP.ordinal()))))
-          .addAlert(Alert.newBuilder()
+            int refractorySecs,
+            long triggerIfSumGt) throws Exception {
+        return ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                // Items of relevance for detecting the anomaly:
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) // Event
+                                // only when the uid is this app's uid.
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
+                                        .setEqInt(SHELL_UID))
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                        .setEqInt(AppBreadcrumbReported.State.START.getNumber()))))
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                // Event only when the uid is this app's uid.
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
+                                        .setEqInt(SHELL_UID))
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                        .setEqInt(AppBreadcrumbReported.State.STOP.getNumber()))))
+                .addAlert(Alert.newBuilder()
                         .setId(ALERT_ID)
                         .setMetricId(METRIC_ID) // The metric itself must yet be added by the test.
                         .setNumBuckets(numBuckets)
                         .setRefractoryPeriodSecs(refractorySecs)
                         .setTriggerIfSumGt(triggerIfSumGt))
-          .addSubscription(
-              Subscription.newBuilder()
-                  .setId(SUBSCRIPTION_ID_INCIDENTD)
-                  .setRuleType(Subscription.RuleType.ALERT)
-                  .setRuleId(ALERT_ID)
-                  .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)))
-          // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
-          .addNoReportMetric(METRIC_ID)
+                .addSubscription(Subscription.newBuilder()
+                        .setId(SUBSCRIPTION_ID_INCIDENTD)
+                        .setRuleType(Subscription.RuleType.ALERT)
+                        .setRuleId(ALERT_ID)
+                        .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(
+                                INCIDENTD_SECTION)))
+                // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
+                .addNoReportMetric(METRIC_ID)
 
-          // Items of relevance to reporting the anomaly (we do want this data):
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(ANOMALY_DETECT_MATCH_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
-                          .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
-                                                    .setEqInt(CONFIG_ID))))
-          .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
-                              .setId(ANOMALY_EVENT_ID)
-                              .setWhat(ANOMALY_DETECT_MATCH_ID));
+                // Items of relevance to reporting the anomaly (we do want this data):
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(ANOMALY_DETECT_MATCH_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
+                                        .setEqInt(SHELL_UID))
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
+                                        .setEqInt(ConfigUtils.CONFIG_ID))))
+                .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
+                        .setId(ANOMALY_EVENT_ID)
+                        .setWhat(ANOMALY_DETECT_MATCH_ID));
+    }
+
+    /**
+     * Determines whether perfetto enabled the kernel ftrace tracer.
+     */
+    protected boolean isSystemTracingEnabled() throws Exception {
+        final String traceFsPath = "/sys/kernel/tracing/tracing_on";
+        String tracing_on = probe(traceFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+
+        // fallback to debugfs
+        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
+                tracing_on);
+
+        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
+        tracing_on = probe(debugFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+        throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
+    }
+
+    private String probe(String path) throws Exception {
+        return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
+                + " cat " + path + " ; else echo -1 ; fi");
+    }
+
+    protected void enableSystemTracing() throws Exception {
+        getDevice().executeShellCommand("setprop persist.traced.enable 1");
+    }
+
+    protected void disableSystemTracing() throws Exception {
+        getDevice().executeShellCommand("setprop persist.traced.enable 0");
+    }
+
+    /**
+     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
+     * run too close of for too many times and hits the upload limit.
+     */
+    private void resetPerfettoGuardrails() throws Exception {
+        final String cmd = "perfetto --reset-guardrails";
+        CommandResult cr = getDevice().executeShellV2Command(cmd);
+        if (cr.getStatus() != CommandStatus.SUCCESS) {
+            throw new Exception(
+                    String.format("Error while executing %s: %s %s", cmd, cr.getStdout(),
+                            cr.getStderr()));
+        }
+    }
+
+    /**
+     * Returns a protobuf-encoded perfetto config that enables the kernel
+     * ftrace tracer with sched_switch for 10 seconds.
+     */
+    private ByteString getPerfettoConfig() {
+        TraceConfig.Builder builder = TraceConfig.newBuilder();
+
+        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
+                .newBuilder()
+                .setSizeKb(128)
+                .build();
+        builder.addBuffers(buffer);
+
+        FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
+                .addFtraceEvents("sched/sched_switch")
+                .build();
+        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
+                .setName("linux.ftrace")
+                .setTargetBuffer(0)
+                .setFtraceConfig(ftraceConfig)
+                .build();
+        TraceConfig.DataSource dataSource = TraceConfig.DataSource
+                .newBuilder()
+                .setConfig(dataSourceConfig)
+                .build();
+        builder.addDataSources(dataSource);
+
+        builder.setDurationMs(10000);
+        builder.setAllowUserBuildTracing(true);
+
+        TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig
+                .newBuilder()
+                .setSkipIncidentd(true)
+                .build();
+        builder.setIncidentReportConfig(incident);
+
+        // To avoid being hit with guardrails firing in multiple test runs back
+        // to back, we set a unique session key for each config.
+        Random random = new Random();
+        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
+        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
+        builder.setUniqueSessionName(sessionNameBuilder.toString());
+
+        return builder.build().toByteString();
     }
 }
diff --git a/tests/src/android/cts/statsd/apex/BootstrapApexTests.java b/tests/src/android/cts/statsd/apex/BootstrapApexTests.java
index ab6093f..2db635a 100644
--- a/tests/src/android/cts/statsd/apex/BootstrapApexTests.java
+++ b/tests/src/android/cts/statsd/apex/BootstrapApexTests.java
@@ -18,11 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.BaseTestCase;
 import com.android.apex.ApexInfo;
 import com.android.apex.XmlParser;
 import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.util.List;
@@ -30,7 +31,7 @@
 /**
  * Verify statsd is not in the bootstrap apexes
  */
-public class BootstrapApexTests extends BaseTestCase {
+public class BootstrapApexTests extends DeviceTestCase {
     private static final String TAG = "Statsd.BootstrapApexTests";
 
     // Constants for the paths to apex-info-list.xml
@@ -39,6 +40,7 @@
     // - legacy location
     private static final String BOOTSTRAP_APEX_FILE2 = "/apex/.bootstrap-apex-info-list.xml";
 
+
     private boolean sdkLevelAtLeast(int sdkLevel, String codename) throws Exception {
         return ApiLevelUtil.isAtLeast(getDevice(), sdkLevel)
                 || ApiLevelUtil.codenameEquals(getDevice(), codename);
diff --git a/tests/src/android/cts/statsd/atom/AtomTestCase.java b/tests/src/android/cts/statsd/atom/AtomTestCase.java
deleted file mode 100644
index b0825a7..0000000
--- a/tests/src/android/cts/statsd/atom/AtomTestCase.java
+++ /dev/null
@@ -1,1265 +0,0 @@
-/*
- * Copyright (C) 2017 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.cts.statsd.atom;
-
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.BatteryStatsProto;
-import android.os.StatsDataDumpProto;
-import android.service.battery.BatteryServiceDumpProto;
-import android.service.batterystats.BatteryStatsServiceDumpProto;
-import android.service.procstats.ProcessStatsServiceDumpProto;
-
-import com.android.annotations.Nullable;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
-import com.android.os.AtomsProto.ProcessStatsProto;
-import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.os.StatsLog.DurationMetricData;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.GaugeBucketInfo;
-import com.android.os.StatsLog.GaugeMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.os.StatsLog.StatsLogReport.GaugeMetricDataWrapper;
-import com.android.os.StatsLog.ValueMetricData;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.Pair;
-
-import com.google.common.collect.Range;
-import com.google.common.io.Files;
-import com.google.protobuf.ByteString;
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Random;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.function.Function;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import perfetto.protos.PerfettoConfig.DataSourceConfig;
-import perfetto.protos.PerfettoConfig.FtraceConfig;
-import perfetto.protos.PerfettoConfig.TraceConfig;
-
-/**
- * Base class for testing Statsd atoms.
- * Validates reporting of statsd logging based on different events
- */
-public class AtomTestCase extends BaseTestCase {
-
-    /**
-     * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
-     * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
-     */
-    public static final boolean OPTIONAL_TESTS_ENABLED = false;
-
-    public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
-    public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
-    public static final String DUMP_BATTERY_CMD = "dumpsys battery";
-    public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
-    public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
-    public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
-    public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
-    /** ID of the config, which evaluates to -1572883457. */
-    public static final long CONFIG_ID = "cts_config".hashCode();
-
-    public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
-    public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
-    public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
-    public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
-    public static final String FEATURE_CAMERA = "android.hardware.camera";
-    public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
-    public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
-    public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
-    public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
-    public static final String FEATURE_PC = "android.hardware.type.pc";
-    public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
-    public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-    public static final String FEATURE_WATCH = "android.hardware.type.watch";
-    public static final String FEATURE_WIFI = "android.hardware.wifi";
-    public static final String FEATURE_INCREMENTAL_DELIVERY =
-            "android.software.incremental_delivery";
-
-    public static final int SHELL_UID = 2000;
-
-    // Telephony phone types
-    public static final int PHONE_TYPE_GSM = 1;
-    public static final int PHONE_TYPE_CDMA = 2;
-    public static final int PHONE_TYPE_CDMA_LTE = 6;
-
-    protected static final int WAIT_TIME_SHORT = 500;
-    protected static final int WAIT_TIME_LONG = 2_000;
-
-    protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
-    protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
-
-    protected static final long NS_PER_SEC = (long) 1E+9;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // Uninstall to clear the history in case it's still on the device.
-        removeConfig(CONFIG_ID);
-        getReportList(); // Clears data.
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        removeConfig(CONFIG_ID);
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        super.tearDown();
-    }
-
-    /**
-     * Determines whether logcat indicates that incidentd fired since the given device date.
-     */
-    protected boolean didIncidentdFireSince(String date) throws Exception {
-        final String INCIDENTD_TAG = "incidentd";
-        final String INCIDENTD_STARTED_STRING = "reportIncident";
-        // TODO: Do something more robust than this in case of delayed logging.
-        Thread.sleep(1000);
-        String log = getLogcatSince(date, String.format(
-                "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
-        return log.contains(INCIDENTD_STARTED_STRING);
-    }
-
-    protected boolean checkDeviceFor(String methodName) throws Exception {
-        try {
-            installPackage(DEVICE_SIDE_TEST_APK, true);
-            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
-            // Test passes, meaning that the answer is true.
-            LogUtil.CLog.d(methodName + "() indicates true.");
-            return true;
-        } catch (AssertionError e) {
-            // Method is designed to fail if the answer is false.
-            LogUtil.CLog.d(methodName + "() indicates false.");
-            return false;
-        }
-    }
-
-    /**
-     * Returns a protobuf-encoded perfetto config that enables the kernel
-     * ftrace tracer with sched_switch for 10 seconds.
-     */
-    protected ByteString getPerfettoConfig() {
-        TraceConfig.Builder builder = TraceConfig.newBuilder();
-
-        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
-            .newBuilder()
-            .setSizeKb(128)
-            .build();
-        builder.addBuffers(buffer);
-
-        FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
-            .addFtraceEvents("sched/sched_switch")
-            .build();
-        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
-            .setName("linux.ftrace")
-            .setTargetBuffer(0)
-            .setFtraceConfig(ftraceConfig)
-            .build();
-        TraceConfig.DataSource dataSource = TraceConfig.DataSource
-            .newBuilder()
-            .setConfig(dataSourceConfig)
-            .build();
-        builder.addDataSources(dataSource);
-
-        builder.setDurationMs(10000);
-        builder.setAllowUserBuildTracing(true);
-
-        TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig
-            .newBuilder()
-            .setSkipIncidentd(true)
-            .build();
-        builder.setIncidentReportConfig(incident);
-
-        // To avoid being hit with guardrails firing in multiple test runs back
-        // to back, we set a unique session key for each config.
-        Random random = new Random();
-        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
-        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
-        builder.setUniqueSessionName(sessionNameBuilder.toString());
-
-        return builder.build().toByteString();
-    }
-
-    /**
-     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
-     * run too close of for too many times and hits the upload limit.
-     */
-    protected void resetPerfettoGuardrails() throws Exception {
-        final String cmd = "perfetto --reset-guardrails";
-        CommandResult cr = getDevice().executeShellV2Command(cmd);
-        if (cr.getStatus() != CommandStatus.SUCCESS)
-            throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
-    }
-
-    private String probe(String path) throws Exception {
-        return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
-                + " cat " + path + " ; else echo -1 ; fi");
-    }
-
-    protected void enableSystemTracing() throws Exception {
-        getDevice().executeShellCommand("setprop persist.traced.enable 1");
-    }
-
-    protected void disableSystemTracing() throws Exception {
-        getDevice().executeShellCommand("setprop persist.traced.enable 0");
-    }
-
-    /**
-     * Determines whether perfetto enabled the kernel ftrace tracer.
-     */
-    protected boolean isSystemTracingEnabled() throws Exception {
-        final String traceFsPath = "/sys/kernel/tracing/tracing_on";
-        String tracing_on = probe(traceFsPath);
-        if (tracing_on.startsWith("0")) return false;
-        if (tracing_on.startsWith("1")) return true;
-
-        // fallback to debugfs
-        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
-                tracing_on);
-
-        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
-        tracing_on = probe(debugFsPath);
-        if (tracing_on.startsWith("0")) return false;
-        if (tracing_on.startsWith("1")) return true;
-        throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
-    }
-
-    protected static StatsdConfig.Builder createConfigBuilder() {
-      return StatsdConfig.newBuilder()
-          .setId(CONFIG_ID)
-          .addAllowedLogSource("AID_SYSTEM")
-          .addAllowedLogSource("AID_BLUETOOTH")
-          // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
-          .addAllowedLogSource("com.android.bluetooth")
-          .addAllowedLogSource("AID_LMKD")
-          .addAllowedLogSource("AID_RADIO")
-          .addAllowedLogSource("AID_ROOT")
-          .addAllowedLogSource("AID_STATSD")
-          .addAllowedLogSource("com.android.systemui")
-          .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
-          .addDefaultPullPackages("AID_RADIO")
-          .addDefaultPullPackages("AID_SYSTEM")
-          .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
-    }
-
-    protected void createAndUploadConfig(int atomTag) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atomTag);
-        uploadConfig(conf);
-    }
-
-    protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
-        uploadConfig(config.build());
-    }
-
-    protected void uploadConfig(StatsdConfig config) throws Exception {
-        LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
-        File configFile = File.createTempFile("statsdconfig", ".config");
-        try {
-            Files.write(config.toByteArray(), configFile);
-            String remotePath = "/data/local/tmp/" + configFile.getName();
-            getDevice().pushFile(configFile, remotePath);
-            getDevice().executeShellCommand(String.join(" ", "cat", remotePath, "|",
-                    UPDATE_CONFIG_CMD, String.valueOf(SHELL_UID), String.valueOf(CONFIG_ID)));
-            getDevice().executeShellCommand("rm " + remotePath);
-        } finally {
-            configFile.delete();
-        }
-    }
-
-    protected void removeConfig(long configId) throws Exception {
-        getDevice().executeShellCommand(
-                String.join(" ", REMOVE_CONFIG_CMD,
-                        String.valueOf(SHELL_UID), String.valueOf(configId)));
-    }
-
-    /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
-    protected List<EventMetricData> getEventMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        return getEventMetricDataList(reportList);
-    }
-
-    /**
-     *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
-     */
-    protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
-            ConfigMetricsReportList configMetricsReportList) {
-        return configMetricsReportList.getReportsList().stream()
-                .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
-     * contain a single report).
-     */
-    protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
-            throws Exception {
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<EventMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-          for (EventMetricData metricData :
-               metric.getEventMetrics().getDataList()) {
-            if (metricData.hasAtom()) {
-              data.add(metricData);
-            } else {
-              data.addAll(backfillAggregatedAtomsInEventMetric(metricData));
-            }
-          }
-        }
-        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
-
-        LogUtil.CLog.d("Get EventMetricDataList as following:\n");
-        for (EventMetricData d : data) {
-            LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
-        }
-        return data;
-    }
-
-    protected List<Atom> getGaugeMetricDataList() throws Exception {
-        return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
-    }
-
-    protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-
-        // only config
-        ConfigMetricsReport report = reportList.getReports(0);
-        assertThat(report.getMetricsCount()).isEqualTo(1);
-
-        List<Atom> data = new ArrayList<>();
-        for (GaugeMetricData gaugeMetricData :
-                report.getMetrics(0).getGaugeMetrics().getDataList()) {
-            assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
-            GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
-            if (bucketInfo.getAtomCount() != 0) {
-                for (Atom atom : bucketInfo.getAtomList()) {
-                    data.add(atom);
-                }
-            } else {
-                backFillGaugeBucketAtoms(bucketInfo.getAggregatedAtomInfoList());
-            }
-            if (checkTimestampTruncated) {
-                for (long timestampNs : bucketInfo.getElapsedTimestampNanosList()) {
-                    assertTimestampIsTruncated(timestampNs);
-                }
-            }
-        }
-
-        LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
-        for (Atom d : data) {
-            LogUtil.CLog.d("Atom:\n" + d.toString());
-        }
-        return data;
-    }
-
-    private List<Atom> backFillGaugeBucketAtoms(
-            List<StatsLog.AggregatedAtomInfo> atomInfoList) {
-        List<Pair<Atom, Long>> atomTimestamp = new ArrayList<>();
-        for (StatsLog.AggregatedAtomInfo atomInfo : atomInfoList) {
-            for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
-                atomTimestamp.add(Pair.create(atomInfo.getAtom(), timestampNs));
-            }
-        }
-        atomTimestamp.sort(Comparator.comparing(o -> o.second));
-        return atomTimestamp.stream().map(p -> p.first).collect(Collectors.toList());
-    }
-
-    protected GaugeMetricDataWrapper backfillGaugeMetricData(GaugeMetricDataWrapper dataWrapper) {
-        GaugeMetricDataWrapper.Builder dataWrapperBuilder = dataWrapper.toBuilder();
-        List<GaugeMetricData> backfilledMetricData = new ArrayList<>();
-        for (GaugeMetricData gaugeMetricData : dataWrapperBuilder.getDataList()) {
-            GaugeMetricData.Builder gaugeMetricDataBuilder = gaugeMetricData.toBuilder();
-            List<GaugeBucketInfo> backfilledBuckets = new ArrayList<>();
-            for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) {
-                backfilledBuckets.add(backfillGaugeBucket(bucketInfo.toBuilder()));
-            }
-            gaugeMetricDataBuilder.clearBucketInfo();
-            gaugeMetricDataBuilder.addAllBucketInfo(backfilledBuckets);
-            backfilledMetricData.add(gaugeMetricDataBuilder.build());
-        }
-        dataWrapperBuilder.clearData();
-        dataWrapperBuilder.addAllData(backfilledMetricData);
-        return dataWrapperBuilder.build();
-    }
-
-    private GaugeBucketInfo backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder) {
-        if (bucketInfoBuilder.getAtomCount() != 0) {
-            return bucketInfoBuilder.build();
-        }
-        List<Pair<Atom, Long>> atomTimestampData = new ArrayList<>();
-        for (StatsLog.AggregatedAtomInfo atomInfo : bucketInfoBuilder.getAggregatedAtomInfoList()) {
-            for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
-                atomTimestampData.add(Pair.create(atomInfo.getAtom(), timestampNs));
-            }
-        }
-        atomTimestampData.sort(Comparator.comparing(o -> o.second));
-        bucketInfoBuilder.clearAggregatedAtomInfo();
-        for (Pair<Atom, Long> atomTimestamp : atomTimestampData) {
-            bucketInfoBuilder.addAtom(atomTimestamp.first);
-            bucketInfoBuilder.addElapsedTimestampNanos(atomTimestamp.second);
-        }
-        return bucketInfoBuilder.build();
-    }
-
-    /**
-     * Gets the statsd report and extract duration metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<DurationMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getDurationMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
-        for (DurationMetricData d : data) {
-            LogUtil.CLog.d("Duration " + d);
-        }
-        return data;
-    }
-
-    /**
-     * Gets the statsd report and extract count metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<CountMetricData> getCountMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<CountMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getCountMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got CountMetricDataList as following:\n");
-        for (CountMetricData d : data) {
-            LogUtil.CLog.d("Count " + d);
-        }
-        return data;
-    }
-
-    /**
-     * Gets the statsd report and extract value metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<ValueMetricData> getValueMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<ValueMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getValueMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
-        for (ValueMetricData d : data) {
-            LogUtil.CLog.d("Value " + d);
-        }
-        return data;
-    }
-
-    protected StatsLogReport getStatsLogReport() throws Exception {
-        ConfigMetricsReport report = getConfigMetricsReport();
-        assertThat(report.hasUidMap()).isTrue();
-        assertThat(report.getMetricsCount()).isEqualTo(1);
-        return report.getMetrics(0);
-    }
-
-    protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        return reportList.getReports(0);
-    }
-
-    /** Gets the statsd report. Note that this also deletes that report from statsd. */
-    protected ConfigMetricsReportList getReportList() throws Exception {
-        try {
-            ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
-                    String.join(" ", DUMP_REPORT_CMD, String.valueOf(SHELL_UID),
-                            String.valueOf(CONFIG_ID), "--include_current_bucket", "--proto"));
-            return reportList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
-                    + "Perhaps there is not a valid statsd config for the requested "
-                    + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
-            throw (e);
-        }
-    }
-
-    protected BatteryStatsProto getBatteryStatsProto() throws Exception {
-        try {
-            BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_BATTERYSTATS_CMD,
-                            "--proto")).getBatterystats();
-            LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
-            return batteryStatsProto;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump batterystats proto");
-            throw (e);
-        }
-    }
-
-    /** Gets reports from the statsd data incident section from the stats dumpsys. */
-    protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
-        try {
-            StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
-                    String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
-            // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
-            List<ConfigMetricsReportList> reports
-                    = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
-            for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
-                reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
-            }
-            LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
-            return reports;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dumpsys stats proto");
-            throw (e);
-        }
-    }
-
-    protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
-        try {
-
-            List<ProcessStatsProto> processStatsProtoList =
-                new ArrayList<ProcessStatsProto>();
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    ProcessStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--proto")).getProcstatsNow();
-            for (android.service.procstats.ProcessStatsProto stats :
-                    sectionProto.getProcessStatsList()) {
-                ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
-                    stats.toByteArray());
-                processStatsProtoList.add(procStats);
-            }
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (ProcessStatsProto processStatsProto : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    /*
-     * Get all procstats package data in proto
-     */
-    protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
-        try {
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    ProcessStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--proto")).getProcstatsOver24Hrs();
-            List<ProcessStatsPackageProto> processStatsProtoList =
-                new ArrayList<ProcessStatsPackageProto>();
-            for (android.service.procstats.ProcessStatsPackageProto pkgStast :
-                sectionProto.getPackageStatsList()) {
-              ProcessStatsPackageProto pkgAtom =
-                  ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
-                processStatsProtoList.add(pkgAtom);
-            }
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    /*
-     * Get all processes' procstats statsd data in proto
-     */
-    protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
-            throws Exception {
-        try {
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    android.service.procstats.ProcessStatsSectionProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--statsd"));
-            List<android.service.procstats.ProcessStatsProto> processStatsProtoList
-                    = sectionProto.getProcessStatsList();
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (android.service.procstats.ProcessStatsProto processStatsProto
-                    : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    protected boolean hasBattery() throws Exception {
-        try {
-            BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
-                    String.join(" ", DUMP_BATTERY_CMD, "--proto"));
-            LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
-            return batteryProto.getIsPresent();
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump batteryservice proto");
-            throw (e);
-        }
-    }
-
-    /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
-    protected static FieldValueMatcher.Builder createFvm(int field) {
-        return FieldValueMatcher.newBuilder().setField(field);
-    }
-
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
-        addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given key.
-     *
-     * @param conf    configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param fvm     FieldValueMatcher.Builder for the relevant key
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
-            FieldValueMatcher.Builder fvm)
-            throws Exception {
-        addAtomEvent(conf, atomTag, Arrays.asList(fvm));
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given keys.
-     *
-     * @param conf   configuration
-     * @param atomId atom tag (from atoms.proto)
-     * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
-            List<FieldValueMatcher.Builder> fvms) throws Exception {
-
-        final String atomName = "Atom" + System.nanoTime();
-        final String eventName = "Event" + System.nanoTime();
-
-        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
-        if (fvms != null) {
-            for (FieldValueMatcher.Builder fvm : fvms) {
-                sam.addFieldValueMatcher(fvm);
-            }
-        }
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(atomName.hashCode())
-                .setSimpleAtomMatcher(sam));
-        conf.addEventMetric(EventMetric.newBuilder()
-                .setId(eventName.hashCode())
-                .setWhat(atomName.hashCode()));
-    }
-
-    /**
-     * Adds an atom to a gauge metric of a config
-     *
-     * @param conf        configuration
-     * @param atomId      atom id (from atoms.proto)
-     * @param gaugeMetric the gauge metric to add
-     */
-    protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
-            GaugeMetric.Builder gaugeMetric) throws Exception {
-        final String atomName = "Atom" + System.nanoTime();
-        final String gaugeName = "Gauge" + System.nanoTime();
-        final String predicateName = "APP_BREADCRUMB";
-        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(atomName.hashCode())
-                .setSimpleAtomMatcher(sam));
-        final String predicateTrueName = "APP_BREADCRUMB_1";
-        final String predicateFalseName = "APP_BREADCRUMB_2";
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(predicateTrueName.hashCode())
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                .setEqInt(1)
-                        )
-                )
-        )
-                // Used to trigger predicate
-                .addAtomMatcher(AtomMatcher.newBuilder()
-                        .setId(predicateFalseName.hashCode())
-                        .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                        .setEqInt(2)
-                                )
-                        )
-                );
-        conf.addPredicate(Predicate.newBuilder()
-                .setId(predicateName.hashCode())
-                .setSimplePredicate(SimplePredicate.newBuilder()
-                        .setStart(predicateTrueName.hashCode())
-                        .setStop(predicateFalseName.hashCode())
-                        .setCountNesting(false)
-                )
-        );
-        gaugeMetric
-                .setId(gaugeName.hashCode())
-                .setWhat(atomName.hashCode())
-                .setCondition(predicateName.hashCode());
-        conf.addGaugeMetric(gaugeMetric.build());
-    }
-
-    /**
-     * Adds an atom to a gauge metric of a config
-     *
-     * @param conf      configuration
-     * @param atomId    atom id (from atoms.proto)
-     * @param dimension dimension is needed for most pulled atoms
-     */
-    protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
-            @Nullable FieldMatcher.Builder dimension) throws Exception {
-        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
-                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
-                .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
-                .setMaxNumGaugeAtomsPerBucket(10000)
-                .setBucket(TimeUnit.CTS);
-        if (dimension != null) {
-            gaugeMetric.setDimensionsInWhat(dimension.build());
-        }
-        addGaugeAtom(conf, atomId, gaugeMetric);
-    }
-
-    /**
-     * Asserts that each set of states in stateSets occurs at least once in data.
-     * Asserts that the states in data occur in the same order as the sets in stateSets.
-     *
-     * @param stateSets        A list of set of states, where each set represents an equivalent
-     *                         state of the device for the purpose of CTS.
-     * @param data             list of EventMetricData from statsd, produced by
-     *                         getReportMetricListData()
-     * @param wait             expected duration (in ms) between state changes; asserts that the
-     *                         actual wait
-     *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
-     *                         assertion.
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
-            int wait, Function<Atom, Integer> getStateFromAtom) {
-        // Sometimes, there are more events than there are states.
-        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
-        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
-        int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
-        for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
-            Atom atom = data.get(dataIndex).getAtom();
-            int state = getStateFromAtom.apply(atom);
-            // If state is in the current state set, we do not assert anything.
-            // If it is not, we expect to have transitioned to the next state set.
-            if (stateSets.get(stateSetIndex).contains(state)) {
-                // No need to assert anything. Just log it.
-                LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
-                        + "in stateSetIndex " + stateSetIndex + ":\n"
-                        + data.get(dataIndex).getAtom().toString());
-            } else {
-                stateSetIndex += 1;
-                LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
-                        + " in stateSetIndex " + stateSetIndex + ":\n"
-                        + data.get(dataIndex).getAtom().toString());
-                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
-                assertWithMessage("Too many states").that(stateSetIndex)
-                    .isLessThan(stateSets.size());
-                assertWithMessage(String.format("Is in wrong state (%d)", state))
-                    .that(stateSets.get(stateSetIndex)).contains(state);
-                if (wait > 0) {
-                    assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
-                            wait / 2, wait * 5);
-                }
-            }
-        }
-        assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
-    }
-
-    /**
-     * Removes all elements from data prior to the first occurrence of an element of state. After
-     * this method is called, the first element of data (if non-empty) is guaranteed to be an
-     * element in state.
-     *
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
-            Function<Atom, Integer> getStateFromAtom) {
-        int firstStateIdx;
-        for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
-            Atom atom = data.get(firstStateIdx).getAtom();
-            if (state.contains(getStateFromAtom.apply(atom))) {
-                break;
-            }
-        }
-        if (firstStateIdx == 0) {
-            // First first element already is in state, so there's nothing to do.
-            return;
-        }
-        data.subList(0, firstStateIdx).clear();
-    }
-
-    /**
-     * Removes all elements from data after to the last occurrence of an element of state. After
-     * this method is called, the last element of data (if non-empty) is guaranteed to be an
-     * element in state.
-     *
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
-        Function<Atom, Integer> getStateFromAtom) {
-        int lastStateIdx;
-        for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
-            Atom atom = data.get(lastStateIdx).getAtom();
-            if (state.contains(getStateFromAtom.apply(atom))) {
-                break;
-            }
-        }
-        if (lastStateIdx == data.size()-1) {
-            // Last element already is in state, so there's nothing to do.
-            return;
-        }
-        data.subList(lastStateIdx+1, data.size()).clear();
-    }
-
-    /** Returns the UID of the host, which should always either be SHELL (2000). */
-    protected int getHostUid() throws DeviceNotAvailableException {
-        return SHELL_UID;
-    }
-
-    protected String getProperty(String prop) throws Exception {
-        return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
-    }
-
-    protected void turnScreenOn() throws Exception {
-        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        getDevice().executeShellCommand("wm dismiss-keyguard");
-    }
-
-    protected void turnScreenOff() throws Exception {
-        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
-    }
-
-    protected void setChargingState(int state) throws Exception {
-        getDevice().executeShellCommand("cmd battery set status " + state);
-    }
-
-    protected void unplugDevice() throws Exception {
-        // On batteryless devices on Android P or above, the 'unplug' command
-        // alone does not simulate the really unplugged state.
-        //
-        // This is because charging state is left as "unknown". Unless a valid
-        // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
-        // framework does not consider the device as running on battery.
-        setChargingState(3);
-
-        getDevice().executeShellCommand("cmd battery unplug");
-    }
-
-    protected void plugInAc() throws Exception {
-        getDevice().executeShellCommand("cmd battery set ac 1");
-    }
-
-    protected void plugInUsb() throws Exception {
-        getDevice().executeShellCommand("cmd battery set usb 1");
-    }
-
-    protected void plugInWireless() throws Exception {
-        getDevice().executeShellCommand("cmd battery set wireless 1");
-    }
-
-    protected void enableLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats enable");
-    }
-
-    protected void resetLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats reset");
-    }
-
-    protected void disableLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats disable");
-    }
-
-    protected void enableBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
-    }
-
-    protected void resetBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
-    }
-
-    protected void disableBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
-    }
-
-    protected void binderStatsNoSampling() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
-    }
-
-    protected void setUpLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats enable");
-        getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
-        getDevice().executeShellCommand("cmd looper_stats reset");
-    }
-
-    protected void cleanUpLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats disable");
-    }
-
-    public void setAppBreadcrumbPredicate() throws Exception {
-        doAppBreadcrumbReportedStart(1);
-    }
-
-    public void clearAppBreadcrumbPredicate() throws Exception {
-        doAppBreadcrumbReportedStart(2);
-    }
-
-    public void doAppBreadcrumbReportedStart(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
-    }
-
-    public void doAppBreadcrumbReportedStop(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
-    }
-
-    public void doAppBreadcrumbReported(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-    }
-
-    public void doAppBreadcrumbReported(int label, int state) throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "cmd stats log-app-breadcrumb %d %d %d", SHELL_UID, label, state));
-    }
-
-    protected void setBatteryLevel(int level) throws Exception {
-        getDevice().executeShellCommand("cmd battery set level " + level);
-    }
-
-    protected void resetBatteryStatus() throws Exception {
-        getDevice().executeShellCommand("cmd battery reset");
-    }
-
-    protected int getScreenBrightness() throws Exception {
-        return Integer.parseInt(
-                getDevice().executeShellCommand("settings get system screen_brightness").trim());
-    }
-
-    protected void setScreenBrightness(int brightness) throws Exception {
-        getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
-    }
-
-    // Gets whether "Always on Display" setting is enabled.
-    // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
-    protected String getAodState() throws Exception {
-        return getDevice().executeShellCommand("settings get secure doze_always_on");
-    }
-
-    protected void setAodState(String state) throws Exception {
-        getDevice().executeShellCommand("settings put secure doze_always_on " + state);
-    }
-
-    protected boolean isScreenBrightnessModeManual() throws Exception {
-        String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
-        return Integer.parseInt(mode.trim()) == 0;
-    }
-
-    protected void setScreenBrightnessMode(boolean manual) throws Exception {
-        getDevice().executeShellCommand(
-                "settings put system screen_brightness_mode " + (manual ? 0 : 1));
-    }
-
-    protected void enterDozeModeLight() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
-    }
-
-    protected void enterDozeModeDeep() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
-    }
-
-    protected void leaveDozeMode() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle unforce");
-        getDevice().executeShellCommand("dumpsys deviceidle disable");
-        getDevice().executeShellCommand("dumpsys deviceidle enable");
-    }
-
-    protected void turnBatterySaverOn() throws Exception {
-        unplugDevice();
-        getDevice().executeShellCommand("settings put global low_power 1");
-    }
-
-    protected void turnBatterySaverOff() throws Exception {
-        getDevice().executeShellCommand("settings put global low_power 0");
-        getDevice().executeShellCommand("cmd battery reset");
-    }
-
-    protected void turnBatteryStatsAutoResetOn() throws Exception {
-        getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset");
-    }
-
-    protected void turnBatteryStatsAutoResetOff() throws Exception {
-        getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset");
-    }
-
-    protected void flushBatteryStatsHandlers() throws Exception {
-        // Dumping batterystats will flush everything in the batterystats handler threads.
-        getDevice().executeShellCommand(DUMP_BATTERYSTATS_CMD);
-    }
-
-    protected void rebootDevice() throws Exception {
-        getDevice().rebootUntilOnline();
-    }
-
-    /**
-     * Asserts that the two events are within the specified range of each other.
-     *
-     * @param d0        the event that should occur first
-     * @param d1        the event that should occur second
-     * @param minDiffMs d0 should precede d1 by at least this amount
-     * @param maxDiffMs d0 should precede d1 by at most this amount
-     */
-    public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
-            int minDiffMs, int maxDiffMs) {
-        long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
-        assertWithMessage("Illegal time difference")
-            .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
-    }
-
-    protected String getCurrentLogcatDate() throws Exception {
-        // TODO: Do something more robust than this for getting logcat markers.
-        long timestampMs = getDevice().getDeviceDate();
-        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
-                .format(new Date(timestampMs));
-    }
-
-    protected String getLogcatSince(String date, String logcatParams) throws Exception {
-        return getDevice().executeShellCommand(String.format(
-                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
-    }
-
-    // TODO: Remove this and migrate all usages to createConfigBuilder()
-    protected StatsdConfig.Builder getPulledConfig() {
-        return createConfigBuilder();
-    }
-    /**
-     * Determines if the device has the given feature.
-     * Prints a warning if its value differs from requiredAnswer.
-     */
-    protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
-        final String features = getDevice().executeShellCommand("pm list features");
-        StringTokenizer featureToken = new StringTokenizer(features, "\n");
-        boolean hasIt = false;
-
-        while (featureToken.hasMoreTokens()) {
-            if (("feature:" + featureName).equals(featureToken.nextToken())) {
-                 hasIt = true;
-                 break;
-            }
-        }
-
-        if (hasIt != requiredAnswer) {
-            LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
-                    + featureName);
-        }
-        return hasIt == requiredAnswer;
-    }
-
-    /**
-     * Determines if the device has |file|.
-     */
-    protected boolean doesFileExist(String file) throws Exception {
-        return getDevice().doesFileExist(file);
-    }
-
-    protected void turnOnAirplaneMode() throws Exception {
-        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
-    }
-
-    protected void turnOffAirplaneMode() throws Exception {
-        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
-    }
-
-    /**
-     * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
-     * output.
-     *
-     * <p>Telephony dumpsys output does not support proto at the moment. This method provides
-     * limited support for parsing its output. Specifically, it does not support arrays or
-     * multi-line values.
-     */
-    private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception {
-        // Matches any line with indentation, except for lines with only spaces
-        Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$");
-        // Matches pattern for class, e.g. "    Phone:"
-        Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
-        // Matches pattern for key-value pairs, e.g. "     mPhoneId=1"
-        Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
-        String response =
-                getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
-        Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
-
-        List<Map<String, String>> results = new ArrayList<>();
-        while (responseLines.peek() != null) {
-            Matcher matcher = classNamePattern.matcher(responseLines.poll());
-            if (matcher.matches()) {
-                final int classIndentLevel = matcher.group(1).length();
-                final Map<String, String> instanceEntries = new HashMap<>();
-                while (responseLines.peek() != null) {
-                    // Skip blank lines
-                    matcher = indentPattern.matcher(responseLines.peek());
-                    if (responseLines.peek().length() == 0 || !matcher.matches()) {
-                        responseLines.poll();
-                        continue;
-                    }
-                    // Finish (without consuming the line) if already parsed past this instance
-                    final int indentLevel = matcher.group(1).length();
-                    if (indentLevel <= classIndentLevel) {
-                        break;
-                    }
-                    // Parse key-value pair if it belongs to the instance directly
-                    matcher = keyValuePattern.matcher(responseLines.poll());
-                    if (indentLevel == classIndentLevel + 1 && matcher.matches()) {
-                        instanceEntries.put(matcher.group(2), matcher.group(3));
-                    }
-                }
-                results.add(instanceEntries);
-            }
-        }
-        return results;
-    }
-
-    protected int getActiveSimSlotCount() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count();
-        return Math.toIntExact(count);
-    }
-
-    /**
-     * Returns the upper bound of active SIM profile count.
-     *
-     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
-     */
-    protected int getActiveSimCountUpperBound() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot ->
-                "true".equals(slot.get("mActive"))
-                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count();
-        return Math.toIntExact(count);
-    }
-
-    /**
-     * Returns the upper bound of active eSIM profile count.
-     *
-     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
-     */
-    protected int getActiveEsimCountUpperBound() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot ->
-                "true".equals(slot.get("mActive"))
-                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))
-                && "true".equals(slot.get("mIsEuicc"))).count();
-        return Math.toIntExact(count);
-    }
-
-    protected boolean hasGsmPhone() throws Exception {
-        // Not using log entries or ServiceState in the dump since they may or may not be present,
-        // which can make the test flaky
-        return getTelephonyDumpEntries("Phone").stream()
-                .anyMatch(phone ->
-                        String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()")));
-    }
-
-    protected boolean hasCdmaPhone() throws Exception {
-        // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone()
-        return getTelephonyDumpEntries("Phone").stream()
-                .anyMatch(phone ->
-                        String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()"))
-                        || String.format("%d", PHONE_TYPE_CDMA_LTE)
-                                .equals(phone.get("getPhoneType()")));
-    }
-
-    // Checks that a timestamp has been truncated to be a multiple of 5 min
-    protected void assertTimestampIsTruncated(long timestampNs) {
-        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
-        assertWithMessage("Timestamp is not truncated")
-                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
-    }
-
-    protected List<EventMetricData> backfillAggregatedAtomsInEventMetric(
-            EventMetricData metricData) {
-      if (!metricData.hasAggregatedAtomInfo()) {
-        return Collections.singletonList(metricData);
-      }
-      List<EventMetricData> data = new ArrayList<>();
-      StatsLog.AggregatedAtomInfo atomInfo = metricData.getAggregatedAtomInfo();
-      for (long timestamp : atomInfo.getElapsedTimestampNanosList()) {
-        data.add(EventMetricData.newBuilder()
-                     .setAtom(atomInfo.getAtom())
-                     .setElapsedTimestampNanos(timestamp)
-                     .build());
-      }
-      return data;
-    }
-}
diff --git a/tests/src/android/cts/statsd/atom/BaseTestCase.java b/tests/src/android/cts/statsd/atom/BaseTestCase.java
deleted file mode 100644
index 0c9921e..0000000
--- a/tests/src/android/cts/statsd/atom/BaseTestCase.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.validation.ValidationTestUtil;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestDescription;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import com.google.protobuf.Parser;
-
-import java.io.FileNotFoundException;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-// Largely copied from incident's ProtoDumpTestCase
-public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
-
-    protected IBuildInfo mCtsBuild;
-
-    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        assertThat(mCtsBuild).isNotNull();
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    public IBuildInfo getBuild() {
-        return mCtsBuild;
-    }
-
-    /**
-     * Create and return {@link ValidationTestUtil} and give it the current build.
-     */
-    public ValidationTestUtil createValidationUtil() {
-        ValidationTestUtil util = new ValidationTestUtil();
-        util.setBuild(getBuild());
-        return util;
-    }
-
-    /**
-     * Call onto the device with an adb shell command and get the results of
-     * that as a proto of the given type.
-     *
-     * @param parser A protobuf parser object. e.g. MyProto.parser()
-     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
-     *
-     * @throws DeviceNotAvailableException If there was a problem communicating with
-     *      the test device.
-     * @throws InvalidProtocolBufferException If there was an error parsing
-     *      the proto. Note that a 0 length buffer is not necessarily an error.
-     */
-    public <T extends MessageLite> T getDump(Parser<T> parser, String command)
-            throws DeviceNotAvailableException, InvalidProtocolBufferException {
-        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-        getDevice().executeShellCommand(command, receiver);
-        if (false) {
-            CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
-                    + " for command: " + command + "\n"
-                    + BufferDebug.debugString(receiver.getOutput(), -1));
-        }
-        try {
-            return parser.parseFrom(receiver.getOutput());
-        } catch (Exception ex) {
-            CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for command: "
-                    + command
-                    + BufferDebug.debugString(receiver.getOutput(), 16384));
-            throw ex;
-        }
-    }
-
-    /**
-     * Install a device side test package.
-     *
-     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
-     * @param grantPermissions whether to give runtime permissions.
-     */
-    protected void installPackage(String appFileName, boolean grantPermissions)
-            throws FileNotFoundException, DeviceNotAvailableException {
-        CLog.d("Installing app " + appFileName);
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        final String result = getDevice().installPackage(
-                buildHelper.getTestFile(appFileName), true, grantPermissions);
-        assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
-            .that(result).isNull();
-    }
-
-    protected CompatibilityBuildHelper getBuildHelper() {
-        return new CompatibilityBuildHelper(mCtsBuild);
-    }
-
-    /**
-     * Run a device side test.
-     *
-     * @param pkgName Test package name, such as "com.android.server.cts.netstats".
-     * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
-     * @param testMethodName Test method name.
-     * @return {@link TestRunResult} of this invocation.
-     * @throws DeviceNotAvailableException
-     */
-    @Nonnull
-    protected TestRunResult runDeviceTests(@Nonnull String pkgName,
-            @Nullable String testClassName, @Nullable String testMethodName)
-            throws DeviceNotAvailableException {
-        if (testClassName != null && testClassName.startsWith(".")) {
-            testClassName = pkgName + testClassName;
-        }
-
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, TEST_RUNNER, getDevice().getIDevice());
-        if (testClassName != null && testMethodName != null) {
-            testRunner.setMethodName(testClassName, testMethodName);
-        } else if (testClassName != null) {
-            testRunner.setClassName(testClassName);
-        }
-
-        CollectingTestListener listener = new CollectingTestListener();
-        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
-
-        final TestRunResult result = listener.getCurrentRunResults();
-        if (result.isRunFailure()) {
-            throw new Error("Failed to successfully run device tests for "
-                    + result.getName() + ": " + result.getRunFailureMessage());
-        }
-        if (result.getNumTests() == 0) {
-            throw new Error("No tests were run on the device");
-        }
-
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
-            }
-            throw new AssertionError(errorBuilder.toString());
-        }
-
-        return result;
-    }
-}
diff --git a/tests/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/tests/src/android/cts/statsd/atom/DeviceAtomTestCase.java
deleted file mode 100644
index d641ebc..0000000
--- a/tests/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2017 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.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.MessageMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
- */
-public class DeviceAtomTestCase extends AtomTestCase {
-
-    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
-    public static final String DEVICE_SIDE_TEST_PACKAGE =
-            "com.android.server.cts.device.statsd";
-    public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10;
-    public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
-            "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
-    private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
-            "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
-    public static final long DEVICE_SIDE_TEST_PKG_HASH =
-            Long.parseUnsignedLong("15694052924544098582");
-
-    // Constants from device side tests (not directly accessible here).
-    public static final String KEY_ACTION = "action";
-    public static final String ACTION_LMK = "action.lmk";
-
-    public static final String CONFIG_NAME = "cts_config";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        installTestApp();
-        Thread.sleep(1000);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        super.tearDown();
-    }
-
-    /**
-     * Performs a device-side test by calling a method on the app and returns its stats events.
-     * @param methodName the name of the method in the app's AtomTests to perform
-     * @param atom atom tag (from atoms.proto)
-     * @param key atom's field corresponding to state
-     * @param stateOn 'on' value
-     * @param stateOff 'off' value
-     * @param minTimeDiffMs max allowed time between start and stop
-     * @param maxTimeDiffMs min allowed time between start and stop
-     * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
-     * @return list of events with the app's uid matching the configuration defined by the params.
-     */
-    protected List<EventMetricData> doDeviceMethodOnOff(
-            String methodName, int atom, int key, int stateOn, int stateOff,
-            int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
-        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
-        List<EventMetricData> data = doDeviceMethod(methodName, conf);
-
-        if (demandExactlyTwo) {
-            assertThat(data).hasSize(2);
-        } else {
-            assertThat(data.size()).isAtLeast(2);
-        }
-        assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
-        return data;
-    }
-
-    /**
-     *
-     * @param methodName the name of the method in the app's AtomTests to perform
-     * @param cfg statsd configuration
-     * @return list of events with the app's uid matching the configuration.
-     */
-    protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
-            throws Exception {
-        removeConfig(CONFIG_ID);
-        getReportList();  // Clears previous data on disk.
-        uploadConfig(cfg);
-        int appUid = getUid();
-        LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
-
-        return getEventMetricDataList();
-    }
-
-    protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atomTag, useAttribution);
-        uploadConfig(conf);
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given key AND has the app's uid.
-     * @param conf configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param fvm FieldValueMatcher.Builder for the relevant key
-     */
-    @Override
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
-            throws Exception {
-
-        final int UID_KEY = 1;
-        FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
-        addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the app's uid.
-     * @param conf configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom
-     * has a uid but not in an attribution node.
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
-            boolean useAttribution) throws Exception {
-        final int UID_KEY = 1;
-        FieldValueMatcher.Builder fvmUid;
-        if (useAttribution) {
-            fvmUid = createAttributionFvm(UID_KEY);
-        } else {
-            fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE);
-        }
-        addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
-    }
-
-    /**
-     * Creates a FieldValueMatcher for atoms that use AttributionNode
-     */
-    protected FieldValueMatcher.Builder createAttributionFvm(int field) {
-        final int ATTRIBUTION_NODE_UID_KEY = 1;
-        return createFvm(field).setPosition(Position.ANY)
-                .setMatchesTuple(MessageMatcher.newBuilder()
-                        .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
-                                .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
-    }
-
-    /**
-     * Gets the uid of the test app.
-     */
-    protected int getUid() throws Exception {
-        int currentUser = getDevice().getCurrentUser();
-        final String packages = getDevice().executeShellCommand("cmd package list packages -U"
-                + " --user " + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
-
-        // Split package list by lines
-        // Sample packages response:
-        // package:com.android.server.cts.device.statsd.host uid:1010033
-        // package:com.android.server.cts.device.statsd uid:1010034
-        final String[] lines = packages.split("[\\r\\n]+");
-        for (final String line : lines) {
-            if (line.startsWith("package:" + DEVICE_SIDE_TEST_PACKAGE + " ")) {
-                final int uidIndex = line.lastIndexOf(":") + 1;
-                final int uid = Integer.parseInt(line.substring(uidIndex).trim());
-                assertThat(uid).isGreaterThan(10_000);
-                return uid;
-            }
-        }
-        throw new Error(
-                String.format("Could not find installed package: %s", DEVICE_SIDE_TEST_PACKAGE));
-    }
-
-    /**
-     * Installs the test apk.
-     */
-    protected void installTestApp() throws Exception {
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
-        allowBackgroundServices();
-    }
-
-    /**
-     * Uninstalls the test apk.
-     */
-    protected void uninstallPackage() throws Exception{
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-    }
-
-    /**
-     * Required to successfully start a background service from adb in Android O.
-     */
-    protected void allowBackgroundServices() throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
-    }
-
-    /**
-     * Runs a (background) service to perform the given action.
-     * @param actionValue the action code constants indicating the desired action to perform.
-     */
-    protected void executeBackgroundService(String actionValue) throws Exception {
-        allowBackgroundServices();
-        getDevice().executeShellCommand(String.format(
-                "am startservice -n '%s' -e %s %s",
-                DEVICE_SIDE_BG_SERVICE_COMPONENT,
-                KEY_ACTION, actionValue));
-    }
-
-
-    /** Make the test app standby-active so it can run syncs and jobs immediately. */
-    protected void allowImmediateSyncs() throws Exception {
-        getDevice().executeShellCommand("am set-standby-bucket "
-                + DEVICE_SIDE_TEST_PACKAGE + " active");
-    }
-
-    /**
-     * Runs the specified activity.
-     */
-    protected void runActivity(String activity, String actionKey, String actionValue)
-            throws Exception {
-        runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
-    }
-
-    /**
-     * Runs the specified activity.
-     */
-    protected void runActivity(String activity, String actionKey, String actionValue,
-            long waitTime) throws Exception {
-        try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
-            Thread.sleep(waitTime);
-        }
-    }
-
-    /**
-     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
-     * when closed.
-     *
-     * <p>Example usage:
-     * <pre>
-     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
-     *         doStuff();
-     *     }
-     * </pre>
-     */
-    protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
-            throws Exception {
-        String intentString = null;
-        if (actionKey != null && actionValue != null) {
-            intentString = actionKey + " " + actionValue;
-        }
-        if (intentString == null) {
-            getDevice().executeShellCommand(
-                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
-        } else {
-            getDevice().executeShellCommand(
-                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
-                            intentString);
-        }
-        return () -> {
-            getDevice().executeShellCommand(
-                    "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
-            Thread.sleep(WAIT_TIME_SHORT);
-        };
-    }
-
-    protected void resetBatteryStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys batterystats --reset");
-    }
-
-    protected void clearProcStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --clear");
-    }
-
-    protected void startProcStatsTesting() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --start-testing");
-    }
-
-    protected void stopProcStatsTesting() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
-    }
-
-    protected void commitProcStatsToDisk() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --commit");
-    }
-
-    protected void rebootDeviceAndWaitUntilReady() throws Exception {
-        rebootDevice();
-        // Wait for 5 mins.
-        assertWithMessage("Device failed to boot")
-            .that(getDevice().waitForBootComplete(300_000)).isTrue();
-        assertWithMessage("Stats service failed to start")
-            .that(waitForStatsServiceStart(60_000)).isTrue();
-        Thread.sleep(2_000);
-    }
-
-    protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
-        LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
-        int counter = 1;
-        long startTime = System.currentTimeMillis();
-        while ((System.currentTimeMillis() - startTime) < waitTime) {
-            if ("running".equals(getProperty("init.svc.statsd"))) {
-                return true;
-            }
-            Thread.sleep(Math.min(200 * counter, 2_000));
-            counter++;
-        }
-        LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
-        return false;
-    }
-
-    boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
-        final String output = getDevice().executeShellCommand(
-                "settings get global netstats_combine_subtype_enabled").trim();
-        return output.equals("1");
-    }
-
-    void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
-        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
-                + (enable ? "1" : "0"));
-    }
-}
diff --git a/tests/src/android/cts/statsd/atom/ProcStateTestCase.java b/tests/src/android/cts/statsd/atom/ProcStateTestCase.java
index 2fa4233..09c508b 100644
--- a/tests/src/android/cts/statsd/atom/ProcStateTestCase.java
+++ b/tests/src/android/cts/statsd/atom/ProcStateTestCase.java
@@ -15,10 +15,20 @@
  */
 package android.cts.statsd.atom;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -31,46 +41,74 @@
 /**
  * Base class for manipulating process states
  */
-public class ProcStateTestCase extends DeviceAtomTestCase {
+public class ProcStateTestCase extends DeviceTestCase implements IBuildReceiver {
 
-  private static final String TAG = "Statsd.ProcStateTestCase";
+    private static final String TAG = "Statsd.ProcStateTestCase";
 
-  private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
-          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
-  private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
-          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
+    private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
+            = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
+    private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
+            = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
 
-  // Constants from the device-side tests (not directly accessible here).
-  public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
-  public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
-  public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
-  public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
-  public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+    private static final String KEY_ACTION = "action";
 
-  // Sleep times (ms) that actions invoke device-side.
-  public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
-  public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
-  public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
-  public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
+    // Constants from the device-side tests (not directly accessible here).
+    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+    public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
+    public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
+    public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
+    public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
 
+    // Sleep times (ms) that actions invoke device-side.
+    public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
+    public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
+    public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
+    public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
 
-  /**
-   * Runs an activity (in the foreground) to perform the given action.
-   * @param actionValue the action code constants indicating the desired action to perform.
-   */
-  protected void executeForegroundActivity(String actionValue) throws Exception {
-    getDevice().executeShellCommand(String.format(
-            "am start -n '%s' -e %s %s",
-            DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
-            KEY_ACTION, actionValue));
-  }
+    protected IBuildInfo mCtsBuild;
 
-  /**
-   * Runs a simple foreground service.
-   */
-  protected void executeForegroundService() throws Exception {
-    executeForegroundActivity(ACTION_END_IMMEDIATELY);
-    getDevice().executeShellCommand(String.format(
-            "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
-  }
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    /**
+     * Runs an activity (in the foreground) to perform the given action.
+     *
+     * @param actionValue the action code constants indicating the desired action to perform.
+     */
+    protected void executeForegroundActivity(String actionValue) throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "am start -n '%s' -e %s %s",
+                DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
+                KEY_ACTION, actionValue));
+    }
+
+    /**
+     * Runs a simple foreground service.
+     */
+    protected void executeForegroundService() throws Exception {
+        executeForegroundActivity(ACTION_END_IMMEDIATELY);
+        getDevice().executeShellCommand(String.format(
+                "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
+    }
 }
diff --git a/tests/src/android/cts/statsd/metadata/MetadataTestCase.java b/tests/src/android/cts/statsd/metadata/MetadataTestCase.java
index ca50270..1f0e5a1 100644
--- a/tests/src/android/cts/statsd/metadata/MetadataTestCase.java
+++ b/tests/src/android/cts/statsd/metadata/MetadataTestCase.java
@@ -16,22 +16,37 @@
 
 package android.cts.statsd.metadata;
 
-import android.cts.statsd.atom.AtomTestCase;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsd.atom.BufferDebug;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.StatsdStatsReport;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-public class MetadataTestCase extends AtomTestCase {
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+public class MetadataTestCase extends DeviceTestCase implements IBuildReceiver {
     public static final String DUMP_METADATA_CMD = "cmd stats print-stats";
 
-    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
-    public static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.device.statsd";
+    protected IBuildInfo mCtsBuild;
 
     protected StatsdStatsReport getStatsdStatsReport() throws Exception {
         try {
-            StatsdStatsReport report = getDump(StatsdStatsReport.parser(),
+            StatsdStatsReport report = MetricsUtils.getDump(getDevice(), StatsdStatsReport.parser(),
                     String.join(" ", DUMP_METADATA_CMD, "--proto"));
             return report;
         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -41,16 +56,33 @@
     }
 
     protected final StatsdConfig.Builder getBaseConfig() throws Exception {
-      StatsdConfig.Builder builder = createConfigBuilder();
-      addAtomEvent(builder, Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
-      return builder;
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        ConfigUtils.addEventMetric(builder, Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+        return builder;
     }
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        Thread.sleep(1000);
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
     }
 }
diff --git a/tests/src/android/cts/statsd/metadata/MetadataTests.java b/tests/src/android/cts/statsd/metadata/MetadataTests.java
index 4b6433e..1f498f9 100644
--- a/tests/src/android/cts/statsd/metadata/MetadataTests.java
+++ b/tests/src/android/cts/statsd/metadata/MetadataTests.java
@@ -18,16 +18,23 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+
 import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.StatsdStatsReport;
 import com.android.os.StatsLog.StatsdStatsReport.ConfigStats;
 import com.android.os.StatsLog.StatsdStatsReport.LogLossStats;
-import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats;
 import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats.LossStatsPerUid;
 import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats.LossStatsPerUid.AtomIdLossStats;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.RunUtil;
+
 import java.util.HashSet;
 
 /**
@@ -37,24 +44,28 @@
 
     private static final String TAG = "Statsd.MetadataTests";
 
+    private static final int SHELL_UID = 2000;
+
     // Tests that the statsd config is reset after the specified ttl.
     public void testConfigTtl() throws Exception {
         final int TTL_TIME_SEC = 8;
         StatsdConfig.Builder config = getBaseConfig();
         config.setTtlInSeconds(TTL_TIME_SEC); // should reset in this many seconds.
 
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
         long startTime = System.currentTimeMillis();
-        Thread.sleep(WAIT_TIME_SHORT);
-        doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, within < TTL_TIME_SEC secs.
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AtomsProto.AppBreadcrumbReported.State.START.getNumber(), /* irrelevant val */
+                6); // Event, within < TTL_TIME_SEC secs.
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         StatsdStatsReport report = getStatsdStatsReport(); // Has only been 1 second
         LogUtil.CLog.d("got following statsdstats report: " + report.toString());
         boolean foundActiveConfig = false;
         int creationTime = 0;
-        for (ConfigStats stats: report.getConfigStatsList()) {
-            if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
-                if(!stats.hasDeletionTimeSec()) {
+        for (ConfigStats stats : report.getConfigStatsList()) {
+            if (stats.getId() == ConfigUtils.CONFIG_ID && stats.getUid() == SHELL_UID) {
+                if (!stats.hasDeletionTimeSec()) {
                     assertWithMessage("Found multiple active CTS configs!")
                             .that(foundActiveConfig).isFalse();
                     foundActiveConfig = true;
@@ -64,17 +75,19 @@
         }
         assertWithMessage("Did not find an active CTS config").that(foundActiveConfig).isTrue();
 
-        while(System.currentTimeMillis() - startTime < 8_000) {
-            Thread.sleep(10);
+        while (System.currentTimeMillis() - startTime < 8_000) {
+            RunUtil.getDefault().sleep(10);
         }
-        doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, after TTL_TIME_SEC secs.
-        Thread.sleep(WAIT_TIME_LONG);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AtomsProto.AppBreadcrumbReported.State.START.getNumber(), /* irrelevant val */
+                6); // Event, after TTL_TIME_SEC secs.
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         report = getStatsdStatsReport();
         LogUtil.CLog.d("got following statsdstats report: " + report.toString());
         foundActiveConfig = false;
         int expectedTime = creationTime + TTL_TIME_SEC;
-        for (ConfigStats stats: report.getConfigStatsList()) {
-            if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
+        for (ConfigStats stats : report.getConfigStatsList()) {
+            if (stats.getId() == ConfigUtils.CONFIG_ID && stats.getUid() == SHELL_UID) {
                 // Original config should be TTL'd
                 if (stats.getCreationTimeSec() == creationTime) {
                     assertWithMessage("Config should have TTL'd but is still active")
@@ -84,7 +97,7 @@
                     ).that(Math.abs(stats.getDeletionTimeSec() - expectedTime)).isAtMost(2);
                 }
                 // There should still be one active config, that is marked as reset.
-                if(!stats.hasDeletionTimeSec()) {
+                if (!stats.hasDeletionTimeSec()) {
                     assertWithMessage("Found multiple active CTS configs!")
                             .that(foundActiveConfig).isFalse();
                     foundActiveConfig = true;
@@ -108,7 +121,8 @@
 
     /** Tests that logging many atoms back to back leads to socket overflow and data loss. */
     public void testAtomLossInfoCollection() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".StatsdStressLogging", "testLogAtomsBackToBack");
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                ".StatsdStressLogging", "testLogAtomsBackToBack");
 
         StatsdStatsReport report = getStatsdStatsReport();
         assertThat(report).isNotNull();
@@ -134,11 +148,12 @@
         for (int i = 0; i < EVENT_STORM_ITERATIONS_COUNT; i++) {
             LogUtil.CLog.d("testSystemServerLossErrorCode iteration #" + i);
             // logging back to back many atoms to force socket overflow
-            runDeviceTests(
-                    DEVICE_SIDE_TEST_PACKAGE, ".StatsdStressLogging", "testLogAtomsBackToBack");
+            DeviceUtils.runDeviceTests(
+                    getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".StatsdStressLogging",
+                    "testLogAtomsBackToBack");
 
             // Delay to allow statsd socket recover after overflow
-            Thread.sleep(500);
+            RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
             // There is some un-deterministic component in AtomLossStats propagation:
             // - the dumpAtomsLossStats() from the libstatssocket happens ONLY after the
@@ -146,10 +161,12 @@
             // - to avoid socket flood there is also cooldown timer incorporated. If no new atoms -
             //   loss info will not be propagated, which is intention by design.
             // Log atoms into socket successfully to trigger libstatsocket dumpAtomsLossStats()
-            doAppBreadcrumbReportedStart(6);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.START.getNumber(), /* irrelevant val */
+                    6); // Event, after TTL_TIME_SEC secs.
 
             // Delay to allow libstatssocket loss info to be propagated to statsdstats
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
 
             StatsdStatsReport report = getStatsdStatsReport();
             assertThat(report).isNotNull();
@@ -198,15 +215,16 @@
         final String appTestPkg = "com.android.statsd.app.atomstorm";
         final String app2TestPkg = "com.android.statsd.app.atomstorm.copy";
 
-        getDevice().uninstallPackage(appTestPkg);
-        getDevice().uninstallPackage(app2TestPkg);
-        installPackage(appTestApk, true);
-        installPackage(app2TestApk, true);
+        DeviceUtils.uninstallTestApp(getDevice(), appTestPkg);
+        DeviceUtils.uninstallTestApp(getDevice(), app2TestPkg);
+
+        DeviceUtils.installTestApp(getDevice(), appTestApk, appTestPkg, mCtsBuild);
+        DeviceUtils.installTestApp(getDevice(), app2TestApk, app2TestPkg, mCtsBuild);
 
         // run reference test app with UID 1
-        runDeviceTests(appTestPkg, null, null);
+        DeviceUtils.runDeviceTests(getDevice(), appTestPkg, null, null);
         // run reference test app with UID 2
-        runDeviceTests(app2TestPkg, null, null);
+        DeviceUtils.runDeviceTests(getDevice(), app2TestPkg, null, null);
 
         StatsdStatsReport report = getStatsdStatsReport();
         assertThat(report).isNotNull();
diff --git a/tests/src/android/cts/statsd/metric/CountMetricsTests.java b/tests/src/android/cts/statsd/metric/CountMetricsTests.java
index f65c48e..8e9b5a3 100644
--- a/tests/src/android/cts/statsd/metric/CountMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/CountMetricsTests.java
@@ -18,48 +18,83 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
 import com.android.internal.os.StatsdConfigProto.Position;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.BleScanStateChanged;
 import com.android.os.AtomsProto.WakelockStateChanged;
 import com.android.os.AttributionNode;
-import com.android.os.StatsLog;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.CountBucketInfo;
 import com.android.os.StatsLog.CountMetricData;
 import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-import java.util.Arrays;
+import com.google.protobuf.ExtensionRegistry;
+
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
-public class CountMetricsTests extends DeviceAtomTestCase {
+public class CountMetricsTests extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     public void testSimpleEventCountMetric() throws Exception {
         int matcherId = 1;
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                .setId(MetricsUtils.COUNT_METRIC_ID)
-                .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                .setWhat(matcherId))
+                        .setId(MetricsUtils.COUNT_METRIC_ID)
+                        .setBucket(StatsdConfigProto.TimeUnit.ONE_MINUTE)
+                        .setWhat(matcherId))
                 .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(100);
-        doAppBreadcrumbReportedStop(0);
-        Thread.sleep(2000);  // Wait for the metrics to propagate to statsd.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(100);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 0);
+        RunUtil.getDefault().sleep(2000);  // Wait for the metrics to propagate to statsd.
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         assertThat(metricReport.hasCountMetrics()).isTrue();
@@ -69,6 +104,7 @@
         assertThat(countData.getDataCount()).isGreaterThan(0);
         assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
     }
+
     public void testEventCountWithCondition() throws Exception {
         int startMatcherId = 1;
         int endMatcherId = 2;
@@ -76,7 +112,7 @@
         int conditionId = 4;
 
         StatsdConfigProto.AtomMatcher whatMatcher =
-               MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
+                MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
 
         StatsdConfigProto.AtomMatcher predicateStartMatcher =
                 MetricsUtils.startAtomMatcher(startMatcherId);
@@ -92,7 +128,8 @@
                 .setId(conditionId)
                 .build();
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
                         .setId(MetricsUtils.COUNT_METRIC_ID)
                         .setBucket(StatsdConfigProto.TimeUnit.CTS)
@@ -103,20 +140,26 @@
                 .addAtomMatcher(predicateEndMatcher)
                 .addPredicate(p);
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStop(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(2000);  // Wait for the metrics to propagate to statsd.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 0);
+        RunUtil.getDefault().sleep(2000);  // Wait for the metrics to propagate to statsd.
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         assertThat(metricReport.hasCountMetrics()).isTrue();
 
@@ -149,7 +192,7 @@
 
         StatsdConfigProto.AtomMatcher activationMatcher =
                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
+                        activationMatcherLabel);
 
         StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder()
                 .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
@@ -159,7 +202,8 @@
                 .setId(conditionId)
                 .build();
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
                         .setId(MetricsUtils.COUNT_METRIC_ID)
                         .setBucket(StatsdConfigProto.TimeUnit.ONE_MINUTE)
@@ -178,66 +222,79 @@
                                 .setAtomMatcherId(activationMatcherId)
                                 .setTtlSeconds(ttlSec)));
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(startMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), startMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should be counted. Bucket 1 Count 1.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should be counted. Bucket 1 Count 2.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(endMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), endMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should not be counted because condition is false.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Condition to true again.
-        doAppBreadcrumbReportedStart(startMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), startMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Event should not be counted, metric is still not active.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         //  Log an event that should be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasCountMetrics()).isTrue();
@@ -252,27 +309,30 @@
 
     public void testPartialBucketCountMetric() throws Exception {
         int matcherId = 1;
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-        builder
-            .addCountMetric(
-                StatsdConfigProto.CountMetric.newBuilder()
-                    .setId(MetricsUtils.COUNT_METRIC_ID)
-                    .setBucket(StatsdConfigProto.TimeUnit.ONE_DAY) // Ensures partial bucket.
-                    .setWhat(matcherId)
-                    .setSplitBucketForAppUpgrade(true))
-            .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
-        uploadConfig(builder);
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
+                        .setId(MetricsUtils.COUNT_METRIC_ID)
+                        .setBucket(StatsdConfigProto.TimeUnit.ONE_DAY) // Ensures partial bucket.
+                        .setWhat(matcherId)
+                        .setSplitBucketForAppUpgrade(true))
+                .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        doAppBreadcrumbReportedStart(0);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
 
         builder.getCountMetricBuilder(0).setBucket(StatsdConfigProto.TimeUnit.CTS);
-        uploadConfig(builder);  // The count metric had a partial bucket.
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(WAIT_TIME_LONG); // Finish the current bucket.
+        ConfigUtils.uploadConfig(getDevice(), builder);  // The count metric had a partial bucket.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); // Finish the current bucket.
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Got following report list: " + reports.toString());
 
         assertThat(reports.getReportsCount()).isEqualTo(2);
@@ -285,9 +345,9 @@
             assertThat(report.getMetrics(0).getCountMetrics().getDataCount()).isEqualTo(1);
         }
         CountMetricData data1 =
-                reports.getReports(inOrder? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
+                reports.getReports(inOrder ? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
         CountMetricData data2 =
-                reports.getReports(inOrder? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
+                reports.getReports(inOrder ? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
         // Data1 should have only 1 bucket, and it should be a partial bucket.
         // The count should be 1.
         assertThat(data1.getBucketInfoCount()).isEqualTo(1);
@@ -326,90 +386,93 @@
                         .build();
 
         StatsdConfigProto.State state = StatsdConfigProto.State.newBuilder()
-            .setId(stateId)
-            .setAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-            .setMap(StatsdConfigProto.StateMap.newBuilder()
-                    .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
-                            .setGroupId(onStateGroupId)
-                            .addValue(WakelockStateChanged.State.ACQUIRE_VALUE)
-                            .addValue(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)
-                    )
-                    .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
-                            .setGroupId(offStateGroupId)
-                            .addValue(WakelockStateChanged.State.RELEASE_VALUE)
-                            .addValue(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)
-                    )
-            )
-            .build();
+                .setId(stateId)
+                .setAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
+                .setMap(StatsdConfigProto.StateMap.newBuilder()
+                        .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
+                                .setGroupId(onStateGroupId)
+                                .addValue(WakelockStateChanged.State.ACQUIRE_VALUE)
+                                .addValue(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)
+                        )
+                        .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
+                                .setGroupId(offStateGroupId)
+                                .addValue(WakelockStateChanged.State.RELEASE_VALUE)
+                                .addValue(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)
+                        )
+                )
+                .build();
 
         StatsdConfigProto.MetricStateLink stateLink = StatsdConfigProto.MetricStateLink.newBuilder()
-            .setStateAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-            .setFieldsInWhat(FieldMatcher.newBuilder()
-                    .setField(whatAtomId)
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(1)
-                            .setPosition(Position.FIRST)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(AttributionNode.UID_FIELD_NUMBER)
-                            )
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(2)
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(3)
-                    )
-            )
-            .setFieldsInState(FieldMatcher.newBuilder()
-                    .setField(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER)
-                            .setPosition(Position.FIRST)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(AttributionNode.UID_FIELD_NUMBER)
-                            )
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.TAG_FIELD_NUMBER)
-                    )
-            )
-            .build();
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                    .setId(MetricsUtils.COUNT_METRIC_ID)
-                    .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                    .setWhat(whatMatcherId)
-                    .addSliceByState(stateId)
-                    .addStateLink(stateLink)
-                    .setDimensionsInWhat(
-                        FieldMatcher.newBuilder()
-                            .setField(whatAtomId)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(1)
-                                    .setPosition(Position.FIRST)
-                                    .addChild(FieldMatcher.newBuilder()
-                                            .setField(AttributionNode.UID_FIELD_NUMBER)
-                                    )
-                            )
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(2)
-                            )
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(3)
+                .setStateAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
+                .setFieldsInWhat(FieldMatcher.newBuilder()
+                        .setField(whatAtomId)
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(1)
+                                .setPosition(Position.FIRST)
+                                .addChild(FieldMatcher.newBuilder()
+                                        .setField(AttributionNode.UID_FIELD_NUMBER)
+                                )
                         )
-                    )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(2)
+                        )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(3)
+                        )
+                )
+                .setFieldsInState(FieldMatcher.newBuilder()
+                        .setField(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(WakelockStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER)
+                                .setPosition(Position.FIRST)
+                                .addChild(FieldMatcher.newBuilder()
+                                        .setField(AttributionNode.UID_FIELD_NUMBER)
+                                )
+                        )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
+                        )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TAG_FIELD_NUMBER)
+                        )
+                )
+                .build();
+
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
+                        .setId(MetricsUtils.COUNT_METRIC_ID)
+                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                        .setWhat(whatMatcherId)
+                        .addSliceByState(stateId)
+                        .addStateLink(stateLink)
+                        .setDimensionsInWhat(
+                                FieldMatcher.newBuilder()
+                                        .setField(whatAtomId)
+                                        .addChild(FieldMatcher.newBuilder()
+                                                .setField(1)
+                                                .setPosition(Position.FIRST)
+                                                .addChild(FieldMatcher.newBuilder()
+                                                        .setField(AttributionNode.UID_FIELD_NUMBER)
+                                                )
+                                        )
+                                        .addChild(FieldMatcher.newBuilder()
+                                                .setField(2)
+                                        )
+                                        .addChild(FieldMatcher.newBuilder()
+                                                .setField(3)
+                                        )
+                        )
                 )
                 .addAtomMatcher(whatMatcher)
                 .addState(state);
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSliceByWakelockState");
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testSliceByWakelockState");
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         assertThat(metricReport.hasCountMetrics()).isTrue();
@@ -420,10 +483,10 @@
 
         List<CountMetricData> sortedDataList = IntStream.range(0, dataWrapper.getDataCount())
                 .mapToObj(i -> {
-                        CountMetricData data = dataWrapper.getData(i);
-                        assertWithMessage("Unexpected SliceByState count for data[%s]", "" + i)
-                                .that(data.getSliceByStateCount()).isEqualTo(1);
-                        return data;
+                    CountMetricData data = dataWrapper.getData(i);
+                    assertWithMessage("Unexpected SliceByState count for data[%s]", "" + i)
+                            .that(data.getSliceByStateCount()).isEqualTo(1);
+                    return data;
                 })
                 .sorted((data1, data2) ->
                         Long.compare(data1.getSliceByState(0).getGroupId(),
diff --git a/tests/src/android/cts/statsd/metric/DurationMetricsTests.java b/tests/src/android/cts/statsd/metric/DurationMetricsTests.java
index 65cef95..89c9bbc 100644
--- a/tests/src/android/cts/statsd/metric/DurationMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/DurationMetricsTests.java
@@ -17,45 +17,62 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
 import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.DurationBucketInfo;
 import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
 
 import com.google.common.collect.Range;
+import com.google.protobuf.ExtensionRegistry;
 
-import java.util.List;
 
-public class DurationMetricsTests extends DeviceAtomTestCase {
+public class DurationMetricsTests extends DeviceTestCase {
 
     private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
     private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
     private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
     private static final int APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID = 3;
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
+
     public void testDurationMetric() throws Exception {
         final int label = 1;
         // Add AtomMatchers.
         AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, label);
+                MetricsUtils.startAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID,
+                        label);
         AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, label);
+                MetricsUtils.stopAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID,
+                        label);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addAtomMatcher(startAtomMatcher);
         builder.addAtomMatcher(stopAtomMatcher);
 
@@ -65,31 +82,33 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add DurationMetric.
-        builder.addDurationMetric(
-            StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                 .setId(MetricsUtils.DURATION_METRIC_ID)
                 .setWhat(predicate.getId())
                 .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
                 .setBucket(StatsdConfigProto.TimeUnit.CTS));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Create AppBreadcrumbReported Start/Stop events.
-        doAppBreadcrumbReportedStart(label);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(label);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), label);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), label);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -97,7 +116,7 @@
                 = metricReport.getDurationMetrics();
         assertThat(durationData.getDataCount()).isEqualTo(1);
         assertThat(durationData.getData(0).getBucketInfo(0).getDurationNanos())
-                .isIn(Range.open(0L, (long)1e9));
+                .isIn(Range.open(0L, (long) 1e9));
     }
 
     public void testDurationMetricWithCondition() throws Exception {
@@ -114,7 +133,8 @@
         AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
                 APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        DeviceUtils.STATSD_ATOM_TEST_PKG)
                 .addAtomMatcher(startAtomMatcher)
                 .addAtomMatcher(stopAtomMatcher)
                 .addAtomMatcher(conditionStartAtomMatcher)
@@ -126,76 +146,82 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
 
         SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
                 .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
                 .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
                 .build();
         Predicate conditionPredicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("ConditionPredicate"))
-                                  .setSimplePredicate(conditionSimplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("ConditionPredicate"))
+                .setSimplePredicate(conditionSimplePredicate)
+                .build();
 
-        builder
-            .addPredicate(predicate)
-            .addPredicate(conditionPredicate);
+        builder.addPredicate(predicate).addPredicate(conditionPredicate);
 
         // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
-                        .setId(MetricsUtils.DURATION_METRIC_ID)
-                        .setWhat(predicate.getId())
-                        .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setCondition(conditionPredicate.getId())
-                );
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
+                .setId(MetricsUtils.DURATION_METRIC_ID)
+                .setWhat(predicate.getId())
+                .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .setCondition(conditionPredicate.getId())
+        );
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
-        StatsLogReport metricReport = getStatsLogReport();
+        RunUtil.getDefault().sleep(2_000);
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -204,9 +230,9 @@
         assertThat(durationData.getDataCount()).isEqualTo(1);
         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
+                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long) 1e9)))
                 .sum();
-        assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
+        assertThat(totalDuration).isIn(Range.open((long) 2e9, (long) 3e9));
     }
 
     public void testDurationMetricWithActivation() throws Exception {
@@ -222,9 +248,10 @@
                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
         StatsdConfigProto.AtomMatcher activationMatcher =
                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
+                        activationMatcherLabel);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(startAtomMatcher)
                 .addAtomMatcher(stopAtomMatcher)
                 .addAtomMatcher(activationMatcher);
@@ -235,19 +262,17 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                         .setId(MetricsUtils.DURATION_METRIC_ID)
                         .setWhat(predicate.getId())
                         .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                )
+                        .setBucket(StatsdConfigProto.TimeUnit.CTS))
                 .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
                         .setMetricId(MetricsUtils.DURATION_METRIC_ID)
                         .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
@@ -257,34 +282,40 @@
                                 .setTtlSeconds(ttlSec)));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
-        StatsLogReport metricReport = getStatsLogReport();
+        RunUtil.getDefault().sleep(2_000);
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -293,9 +324,9 @@
         assertThat(durationData.getDataCount()).isEqualTo(1);
         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
+                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long) 1e9)))
                 .sum();
-        assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
+        assertThat(totalDuration).isIn(Range.open((long) 2e9, (long) 3e9));
     }
 
     public void testDurationMetricWithConditionAndActivation() throws Exception {
@@ -316,9 +347,10 @@
                 APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
         StatsdConfigProto.AtomMatcher activationMatcher =
                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
+                        activationMatcherLabel);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(startAtomMatcher)
                 .addAtomMatcher(stopAtomMatcher)
                 .addAtomMatcher(conditionStartAtomMatcher)
@@ -331,9 +363,9 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
@@ -341,20 +373,18 @@
                 .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
                 .build();
         Predicate conditionPredicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("ConditionPredicate"))
-                                  .setSimplePredicate(conditionSimplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("ConditionPredicate"))
+                .setSimplePredicate(conditionSimplePredicate)
+                .build();
         builder.addPredicate(conditionPredicate);
 
         // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                         .setId(MetricsUtils.DURATION_METRIC_ID)
                         .setWhat(predicate.getId())
                         .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
                         .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setCondition(conditionPredicate.getId())
-                )
+                        .setCondition(conditionPredicate.getId()))
                 .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
                         .setMetricId(MetricsUtils.DURATION_METRIC_ID)
                         .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
@@ -364,100 +394,118 @@
                                 .setTtlSeconds(ttlSec)));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
         //doAppBreadcrumbReported(99); // TODO: maybe remove?
-        //Thread.sleep(10);
+        //RunUtil.getDefault().sleep(10);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        // Start uncounted duration
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -466,23 +514,24 @@
         assertThat(durationData.getDataCount()).isEqualTo(1);
         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
+                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long) 1e9)))
                 .sum();
-        assertThat(totalDuration).isIn(Range.open((long)4e9, (long)5e9));
+        assertThat(totalDuration).isIn(Range.open((long) 4e9, (long) 5e9));
     }
 
     public void testDurationMetricWithDimension() throws Exception {
         // Add AtomMatchers.
         AtomMatcher startAtomMatcherA =
-            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
         AtomMatcher stopAtomMatcherA =
-            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
         AtomMatcher startAtomMatcherB =
-            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
         AtomMatcher stopAtomMatcherB =
-            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addAtomMatcher(startAtomMatcherA);
         builder.addAtomMatcher(stopAtomMatcherA);
         builder.addAtomMatcher(startAtomMatcherB);
@@ -494,9 +543,9 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicateA = Predicate.newBuilder()
-                                   .setId(MetricsUtils.StringToId("Predicate_A"))
-                                   .setSimplePredicate(simplePredicateA)
-                                   .build();
+                .setId(MetricsUtils.StringToId("Predicate_A"))
+                .setSimplePredicate(simplePredicateA)
+                .build();
         builder.addPredicate(predicateA);
 
         FieldMatcher.Builder dimensionsBuilder = FieldMatcher.newBuilder()
@@ -504,47 +553,50 @@
         dimensionsBuilder
                 .addChild(FieldMatcher.newBuilder().setField(
                         AppBreadcrumbReported.LABEL_FIELD_NUMBER));
-        Predicate predicateB =
-            Predicate.newBuilder()
+        Predicate predicateB = Predicate.newBuilder()
                 .setId(MetricsUtils.StringToId("Predicate_B"))
                 .setSimplePredicate(SimplePredicate.newBuilder()
-                                        .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                        .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
-                                        .setDimensions(dimensionsBuilder.build())
-                                        .build())
+                        .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                        .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
+                        .setDimensions(dimensionsBuilder.build())
+                        .build())
                 .build();
         builder.addPredicate(predicateB);
 
         // Add DurationMetric.
-        builder.addDurationMetric(
-            StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                 .setId(MetricsUtils.DURATION_METRIC_ID)
                 .setWhat(predicateB.getId())
                 .setCondition(predicateA.getId())
                 .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
                 .setBucket(StatsdConfigProto.TimeUnit.CTS)
                 .setDimensionsInWhat(
-                    FieldMatcher.newBuilder()
-                            .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    AppBreadcrumbReported.LABEL_FIELD_NUMBER))));
+                        FieldMatcher.newBuilder()
+                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                .addChild(FieldMatcher.newBuilder().setField(
+                                        AppBreadcrumbReported.LABEL_FIELD_NUMBER))));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Trigger events.
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStart(2);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(2);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 2);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 2);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         assertThat(metricReport.hasDurationMetrics()).isTrue();
         StatsLogReport.DurationMetricDataWrapper durationData
diff --git a/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java b/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java
index db6a818..a037a69 100644
--- a/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java
@@ -17,7 +17,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.ActivationType;
@@ -25,7 +27,6 @@
 import com.android.internal.os.StatsdConfigProto.EventActivation;
 import com.android.internal.os.StatsdConfigProto.FieldFilter;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
 import com.android.internal.os.StatsdConfigProto.MetricActivation;
 import com.android.internal.os.StatsdConfigProto.Predicate;
@@ -35,166 +36,205 @@
 import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog;
 import com.android.os.StatsLog.GaugeBucketInfo;
 import com.android.os.StatsLog.GaugeMetricData;
 import com.android.os.StatsLog.StatsLogReport;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.Pair;
+import com.android.tradefed.util.RunUtil;
 
-public class GaugeMetricsTests extends DeviceAtomTestCase {
+import com.google.protobuf.ExtensionRegistry;
 
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
-  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
 
-  public void testGaugeMetric() throws Exception {
-      // Add AtomMatcher's.
-      AtomMatcher startAtomMatcher =
-          MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
-      AtomMatcher stopAtomMatcher =
-          MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
-      AtomMatcher atomMatcher =
-          MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+public class GaugeMetricsTests extends DeviceTestCase {
 
-      StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-      builder.addAtomMatcher(startAtomMatcher);
-      builder.addAtomMatcher(stopAtomMatcher);
-      builder.addAtomMatcher(atomMatcher);
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
+    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
 
-      // Add Predicate's.
-      SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                                            .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                                            .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                                            .build();
-      Predicate predicate = Predicate.newBuilder()
-                                .setId(MetricsUtils.StringToId("Predicate"))
-                                .setSimplePredicate(simplePredicate)
-                                .build();
-      builder.addPredicate(predicate);
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
+    }
 
-      // Add GaugeMetric.
-      FieldMatcher fieldMatcher =
-          FieldMatcher.newBuilder().setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID).build();
-      builder.addGaugeMetric(
-          StatsdConfigProto.GaugeMetric.newBuilder()
-              .setId(MetricsUtils.GAUGE_METRIC_ID)
-              .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-              .setCondition(predicate.getId())
-              .setGaugeFieldsFilter(
-                  FieldFilter.newBuilder().setIncludeAll(false).setFields(fieldMatcher).build())
-              .setDimensionsInWhat(
-                  FieldMatcher.newBuilder()
-                      .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                      .addChild(FieldMatcher.newBuilder()
-                                    .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                    .build())
-                      .build())
-              .setBucket(StatsdConfigProto.TimeUnit.CTS)
-              .build());
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
 
-      // Upload config.
-      uploadConfig(builder);
+    public void testGaugeMetric() throws Exception {
+        // Add AtomMatcher's.
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+        AtomMatcher atomMatcher =
+                MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
 
-      // Create AppBreadcrumbReported Start/Stop events.
-      doAppBreadcrumbReportedStart(0);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(2);
-      Thread.sleep(2000);
-      doAppBreadcrumbReportedStop(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(0);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(1);
-      doAppBreadcrumbReportedStart(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(2000);
-      doAppBreadcrumbReportedStop(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(1);
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addAtomMatcher(atomMatcher);
 
-      // Wait for the metrics to propagate to statsd.
-      Thread.sleep(2000);
+        // Add Predicate's.
+        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
+                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
+                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
+                .build();
+        Predicate predicate = Predicate.newBuilder()
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
+        builder.addPredicate(predicate);
 
-      StatsLogReport metricReport = getStatsLogReport();
-      LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
-      assertThat(metricReport.hasGaugeMetrics()).isTrue();
-      StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
-      gaugeData = backfillGaugeMetricData(gaugeData);
-      assertThat(gaugeData.getDataCount()).isEqualTo(1);
+        // Add GaugeMetric.
+        FieldMatcher fieldMatcher = FieldMatcher.newBuilder()
+                .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                .build();
+        builder.addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
+                .setId(MetricsUtils.GAUGE_METRIC_ID)
+                .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                .setCondition(predicate.getId())
+                .setGaugeFieldsFilter(FieldFilter.newBuilder()
+                        .setIncludeAll(false)
+                        .setFields(fieldMatcher)
+                        .build())
+                .setDimensionsInWhat(FieldMatcher.newBuilder()
+                        .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                .build())
+                        .build())
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .build());
 
-      int bucketCount = gaugeData.getData(0).getBucketInfoCount();
-      GaugeMetricData data = gaugeData.getData(0);
-      assertThat(bucketCount).isGreaterThan(2);
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
-      assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
-      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
-              .isEqualTo(0);
-      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
-              .isEqualTo(AppBreadcrumbReported.State.START);
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
-      assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
+        // Create AppBreadcrumbReported Start/Stop events.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 2);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 2);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 2);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 2);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
 
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount-1));
-      assertThat(data.getBucketInfo(bucketCount-1).getAtomCount()).isEqualTo(1);
-      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getLabel())
-              .isEqualTo(2);
-      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getState())
-              .isEqualTo(AppBreadcrumbReported.State.STOP);
-  }
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(2000);
 
-  public void testPulledGaugeMetricWithActivation() throws Exception {
-      // Add AtomMatcher's.
-      int activationAtomMatcherId = 1;
-      int activationAtomMatcherLabel = 1;
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+        assertThat(metricReport.hasGaugeMetrics()).isTrue();
+        StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
+        gaugeData = backfillGaugeMetricData(gaugeData);
+        assertThat(gaugeData.getDataCount()).isEqualTo(1);
 
-      int systemUptimeMatcherId = 2;
-      AtomMatcher activationAtomMatcher =
-              MetricsUtils.appBreadcrumbMatcherWithLabel(
-                      activationAtomMatcherId, activationAtomMatcherLabel);
-      AtomMatcher systemUptimeMatcher =
-              AtomMatcher.newBuilder()
-                      .setId(systemUptimeMatcherId)
-                      .setSimpleAtomMatcher(
-                              SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
-                      .build();
+        int bucketCount = gaugeData.getData(0).getBucketInfoCount();
+        GaugeMetricData data = gaugeData.getData(0);
+        assertThat(bucketCount).isGreaterThan(2);
+        MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
+        assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
+        assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
+                .isEqualTo(0);
+        assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
+                .isEqualTo(AppBreadcrumbReported.State.START);
 
-      StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-      builder.addAtomMatcher(activationAtomMatcher);
-      builder.addAtomMatcher(systemUptimeMatcher);
+        MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
+        assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
 
-      // Add GaugeMetric.
-      builder.addGaugeMetric(
-              StatsdConfigProto.GaugeMetric.newBuilder()
-                      .setId(MetricsUtils.GAUGE_METRIC_ID)
-                      .setWhat(systemUptimeMatcherId)
-                      .setGaugeFieldsFilter(
-                              FieldFilter.newBuilder().setIncludeAll(true).build())
-                      .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                      .build());
+        MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount - 1));
+        assertThat(data.getBucketInfo(bucketCount - 1).getAtomCount()).isEqualTo(1);
+        assertThat(data.getBucketInfo(bucketCount - 1).getAtom(
+                0).getAppBreadcrumbReported().getLabel())
+                .isEqualTo(2);
+        assertThat(data.getBucketInfo(bucketCount - 1).getAtom(
+                0).getAppBreadcrumbReported().getState())
+                .isEqualTo(AppBreadcrumbReported.State.STOP);
+    }
 
-      // Add activation.
-      builder.addMetricActivation(MetricActivation.newBuilder()
-              .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
-              .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-              .addEventActivation(EventActivation.newBuilder()
-                    .setAtomMatcherId(activationAtomMatcherId)
-                    .setTtlSeconds(5)));
+    public void testPulledGaugeMetricWithActivation() throws Exception {
+        // Add AtomMatcher's.
+        int activationAtomMatcherId = 1;
+        int activationAtomMatcherLabel = 1;
 
-      // Upload config.
-      uploadConfig(builder);
+        int systemUptimeMatcherId = 2;
+        AtomMatcher activationAtomMatcher = MetricsUtils.appBreadcrumbMatcherWithLabel(
+                activationAtomMatcherId, activationAtomMatcherLabel);
+        AtomMatcher systemUptimeMatcher = AtomMatcher.newBuilder()
+                .setId(systemUptimeMatcherId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
+                .build();
 
-      // Plenty of time to pull, but we should not keep the data since we are not active.
-      Thread.sleep(20_000);
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(activationAtomMatcher);
+        builder.addAtomMatcher(systemUptimeMatcher);
 
-      StatsLogReport metricReport = getStatsLogReport();
-      LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
-      assertThat(metricReport.hasGaugeMetrics()).isFalse();
-  }
+        // Add GaugeMetric.
+        builder.addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
+                .setId(MetricsUtils.GAUGE_METRIC_ID)
+                .setWhat(systemUptimeMatcherId)
+                .setGaugeFieldsFilter(
+                        FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .build());
+
+        // Add activation.
+        builder.addMetricActivation(MetricActivation.newBuilder()
+                .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
+                .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                .addEventActivation(EventActivation.newBuilder()
+                        .setAtomMatcherId(activationAtomMatcherId)
+                        .setTtlSeconds(5)));
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Plenty of time to pull, but we should not keep the data since we are not active.
+        RunUtil.getDefault().sleep(20_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+        assertThat(metricReport.hasGaugeMetrics()).isFalse();
+    }
 
     public void testPulledGaugeMetricWithConditionAndActivation() throws Exception {
         final int conditionLabel = 2;
@@ -208,17 +248,16 @@
                 APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, conditionLabel);
         AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, conditionLabel);
-        AtomMatcher activationMatcher =
-                MetricsUtils.startAtomMatcherWithLabel(
-                        activationMatcherId, activationMatcherLabel);
-        AtomMatcher whatMatcher =
-                MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
+        AtomMatcher activationMatcher = MetricsUtils.startAtomMatcherWithLabel(
+                activationMatcherId, activationMatcherLabel);
+        AtomMatcher whatMatcher = MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
 
-        StatsdConfig.Builder builder = createConfigBuilder()
-                .addAtomMatcher(conditionStartAtomMatcher)
-                .addAtomMatcher(conditionStopAtomMatcher)
-                .addAtomMatcher(whatMatcher)
-                .addAtomMatcher(activationMatcher);
+        StatsdConfig.Builder builder =
+                ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                        .addAtomMatcher(conditionStartAtomMatcher)
+                        .addAtomMatcher(conditionStopAtomMatcher)
+                        .addAtomMatcher(whatMatcher)
+                        .addAtomMatcher(activationMatcher);
 
         // Add Predicates.
         SimplePredicate simplePredicate = SimplePredicate.newBuilder()
@@ -226,95 +265,102 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add GaugeMetric.
-        builder
-                .addGaugeMetric(GaugeMetric.newBuilder()
+        builder.addGaugeMetric(GaugeMetric.newBuilder()
                         .setId(MetricsUtils.GAUGE_METRIC_ID)
                         .setWhat(whatMatcher.getId())
                         .setBucket(TimeUnit.CTS)
                         .setCondition(predicate.getId())
-                        .setGaugeFieldsFilter(
-                                FieldFilter.newBuilder().setIncludeAll(false).setFields(
-                                        FieldMatcher.newBuilder()
-                                                .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                )
-                        )
-                        .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId))
-                )
+                        .setGaugeFieldsFilter(FieldFilter.newBuilder()
+                                .setIncludeAll(false)
+                                .setFields(FieldMatcher.newBuilder()
+                                        .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)))
+                        .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId)))
                 .addMetricActivation(MetricActivation.newBuilder()
                         .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
                         .addEventActivation(EventActivation.newBuilder()
                                 .setAtomMatcherId(activationMatcherId)
                                 .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                                .setTtlSeconds(ttlSec)
-                        )
-                );
+                                .setTtlSeconds(ttlSec)));
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // This value is collected.
-        doAppBreadcrumbReported(10);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 10);
+        RunUtil.getDefault().sleep(10);
 
         // Ignored; value already collected.
-        doAppBreadcrumbReported(20);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 20);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Value not updated because condition is false.
-        doAppBreadcrumbReported(30);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 30);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Value not collected.
-        doAppBreadcrumbReported(40);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 40);
+        RunUtil.getDefault().sleep(10);
 
         // Condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Value not collected.
-        doAppBreadcrumbReported(50);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 50);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Value collected.
-        doAppBreadcrumbReported(60);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 60);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Value not collected.
-        doAppBreadcrumbReported(70);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 70);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
         assertThat(metricReport.hasGaugeMetrics()).isTrue();
@@ -335,4 +381,42 @@
         assertThat(bucketInfo.getAtomCount()).isEqualTo(1);
         assertThat(bucketInfo.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(60);
     }
+
+    private StatsLogReport.GaugeMetricDataWrapper backfillGaugeMetricData(
+            StatsLogReport.GaugeMetricDataWrapper dataWrapper) {
+        StatsLogReport.GaugeMetricDataWrapper.Builder dataWrapperBuilder = dataWrapper.toBuilder();
+        List<GaugeMetricData> backfilledMetricData = new ArrayList<>();
+        for (GaugeMetricData gaugeMetricData : dataWrapperBuilder.getDataList()) {
+            GaugeMetricData.Builder gaugeMetricDataBuilder = gaugeMetricData.toBuilder();
+            List<GaugeBucketInfo> backfilledBuckets = new ArrayList<>();
+            for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) {
+                backfilledBuckets.add(backfillGaugeBucket(bucketInfo.toBuilder()));
+            }
+            gaugeMetricDataBuilder.clearBucketInfo();
+            gaugeMetricDataBuilder.addAllBucketInfo(backfilledBuckets);
+            backfilledMetricData.add(gaugeMetricDataBuilder.build());
+        }
+        dataWrapperBuilder.clearData();
+        dataWrapperBuilder.addAllData(backfilledMetricData);
+        return dataWrapperBuilder.build();
+    }
+
+    private GaugeBucketInfo backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder) {
+        if (bucketInfoBuilder.getAtomCount() != 0) {
+            return bucketInfoBuilder.build();
+        }
+        List<Pair<Atom, Long>> atomTimestampData = new ArrayList<>();
+        for (StatsLog.AggregatedAtomInfo atomInfo : bucketInfoBuilder.getAggregatedAtomInfoList()) {
+            for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
+                atomTimestampData.add(Pair.create(atomInfo.getAtom(), timestampNs));
+            }
+        }
+        atomTimestampData.sort(Comparator.comparing(o -> o.second));
+        bucketInfoBuilder.clearAggregatedAtomInfo();
+        for (Pair<Atom, Long> atomTimestamp : atomTimestampData) {
+            bucketInfoBuilder.addAtom(atomTimestamp.first);
+            bucketInfoBuilder.addElapsedTimestampNanos(atomTimestamp.second);
+        }
+        return bucketInfoBuilder.build();
+    }
 }
diff --git a/tests/src/android/cts/statsd/metric/MetricActivationTests.java b/tests/src/android/cts/statsd/metric/MetricActivationTests.java
index a95cb1b..5c6f2fb 100644
--- a/tests/src/android/cts/statsd/metric/MetricActivationTests.java
+++ b/tests/src/android/cts/statsd/metric/MetricActivationTests.java
@@ -16,32 +16,39 @@
 package android.cts.statsd.metric;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
-import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.ActivationType;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventActivation;
 import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
 import com.android.internal.os.StatsdConfigProto.MetricActivation;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.StatsLogReport;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.protobuf.ExtensionRegistry;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Test Statsd Metric activations and deactivations
  */
-public class MetricActivationTests extends DeviceAtomTestCase {
+public class MetricActivationTests extends DeviceTestCase {
     private final long metric1Id = 1L;
     private final int metric1MatcherId = 1;
 
@@ -57,6 +64,20 @@
     private final int act2MatcherId = 20;
     private final int act2CancelMatcherId = -20;
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
 
     private StatsdConfig.Builder createConfig(final int act1TtlSecs, final int act2TtlSecs) {
         AtomMatcher metric1Matcher =
@@ -65,49 +86,41 @@
                 MetricsUtils.simpleAtomMatcher(metric2MatcherId, metric2MatcherId);
         AtomMatcher metric3Matcher =
                 MetricsUtils.simpleAtomMatcher(metric3MatcherId, metric3MatcherId);
-        AtomMatcher act1Matcher =
-                MetricsUtils.simpleAtomMatcher(act1MatcherId, act1MatcherId);
+        AtomMatcher act1Matcher = MetricsUtils.simpleAtomMatcher(act1MatcherId, act1MatcherId);
         AtomMatcher act1CancelMatcher =
                 MetricsUtils.simpleAtomMatcher(act1CancelMatcherId, act1CancelMatcherId);
-        AtomMatcher act2Matcher =
-                MetricsUtils.simpleAtomMatcher(act2MatcherId, act2MatcherId);
+        AtomMatcher act2Matcher = MetricsUtils.simpleAtomMatcher(act2MatcherId, act2MatcherId);
         AtomMatcher act2CancelMatcher =
                 MetricsUtils.simpleAtomMatcher(act2CancelMatcherId, act2CancelMatcherId);
 
-        EventMetric metric1 = EventMetric.newBuilder()
-                .setId(metric1Id)
-                .setWhat(metric1MatcherId)
-                .build();
+        EventMetric metric1 =
+                EventMetric.newBuilder().setId(metric1Id).setWhat(metric1MatcherId).build();
 
-        EventMetric metric2 = EventMetric.newBuilder()
-                .setId(metric2Id)
-                .setWhat(metric2MatcherId)
-                .build();
+        EventMetric metric2 =
+                EventMetric.newBuilder().setId(metric2Id).setWhat(metric2MatcherId).build();
 
-        EventMetric metric3 = EventMetric.newBuilder()
-                .setId(metric3Id)
-                .setWhat(metric3MatcherId)
-                .build();
+        EventMetric metric3 =
+                EventMetric.newBuilder().setId(metric3Id).setWhat(metric3MatcherId).build();
 
         EventActivation metric1Act1 =
                 MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                        .build();
 
         EventActivation metric1Act2 =
                 MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
+                        .build();
 
         EventActivation metric2Act1 =
                 MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
+                        .build();
 
         EventActivation metric2Act2 =
                 MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                        .build();
 
         MetricActivation metric1Activation = MetricActivation.newBuilder()
                 .setMetricId(metric1Id)
@@ -121,7 +134,8 @@
                 .addEventActivation(metric2Act2)
                 .build();
 
-        return createConfigBuilder()
+
+        return ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(metric1Matcher)
                 .addAtomMatcher(metric2Matcher)
                 .addAtomMatcher(metric3Matcher)
@@ -138,96 +152,113 @@
 
     /**
      * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 5 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 8 seconds
-     *         - Type: ON_BOOT
+     * Activation 1:
+     * - Ttl: 5 seconds
+     * - Type: IMMEDIATE
+     * Activation 2:
+     * - Ttl: 8 seconds
+     * - Type: ON_BOOT
      *
      * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 5 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 8 seconds
-     *         - Type: IMMEDIATE
+     * Activation 1:
+     * - Ttl: 5 seconds
+     * - Type: ON_BOOT
+     * Activation 2:
+     * - Ttl: 8 seconds
+     * - Type: IMMEDIATE
      *
      * Metric 3: No activations; always active
      **/
     public void testCancellation() throws Exception {
         final int act1TtlSecs = 5;
         final int act2TtlSecs = 8;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
+        ConfigUtils.uploadConfig(getDevice(), createConfig(act1TtlSecs, act2TtlSecs));
 
         // Ignored, metric not active.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger cancel for already inactive event activation 1.
-        doAppBreadcrumbReported(act1CancelMatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1CancelMatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 1.
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Second logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Cancel event activation 1.
-        doAppBreadcrumbReported(act1CancelMatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1CancelMatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Ignored, metric not active.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 1.
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 2.
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Third logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Cancel event activation 2.
-        doAppBreadcrumbReported(act2CancelMatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2CancelMatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Fourth logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Expire event activation 1
-        Thread.sleep(act1TtlSecs * 1000);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000);
 
         // Ignored, metric 1 not active. Activation 1 expired and Activation 2 was cancelled.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 2.
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Metric 1 log ignored, Activation 1 expired and Activation 2 needs reboot to activate.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event for Metric 3.
-        doAppBreadcrumbReported(metric3MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric3MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
-        ConfigMetricsReportList reportList = getReportList();
+        ConfigMetricsReportList reportList = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
         ConfigMetricsReport report = reports.get(0);
         verifyMetrics(report, 4, 0, 1);
@@ -235,27 +266,27 @@
 
     /**
      * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: ON_BOOT
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: IMMEDIATE
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: ON_BOOT
      *
      * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: IMMEDIATE
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: ON_BOOT
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: IMMEDIATE
      *
      * Metric 3: No activations; always active
      **/
     public void testRestart() throws Exception {
         final int act1TtlSecs = 200;
         final int act2TtlSecs = 400;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
+        ConfigUtils.uploadConfig(getDevice(), createConfig(act1TtlSecs, act2TtlSecs));
 
         // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
         // Time remaining:
@@ -263,8 +294,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -276,7 +308,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
 
         // Second logged event for Metric 1.
         // First logged event for Metric 2.
@@ -288,7 +320,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L);
 
         // Metric 1 event ignored, Activation 1 expired.
         // Metric 2 event ignored, Activation 1 expired.
@@ -301,8 +333,9 @@
         // Metric 1 Activation 2: 0 seconds (will activate after boot)
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 400 seconds
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Metric 1 event ignored, will activate after boot.
         // Second logged event for Metric 2.
@@ -315,8 +348,9 @@
         // Metric 1 Activation 2: 0 seconds (will activate after boot)
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 400 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Third logged event for Metric 1.
         // Third logged event for Metric 2.
@@ -328,14 +362,14 @@
         // Metric 1 Activation 2: 0 seconds (will activate after boot)
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 300 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L / 2);
 
         // Time remaining:
         // Metric 1 Activation 1: 100 seconds
         // Metric 1 Activation 2: 400 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 300 seconds
-        rebootDeviceAndWaitUntilReady();
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
 
         // Fourth logged event for Metric 1.
         // Fourth logged event for Metric 2.
@@ -348,7 +382,7 @@
         // Metric 1 Activation 2: 300 seconds
         // Metric 2 Activation 1: 100 seconds
         // Metric 2 Activation 2: 200 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L / 2);
 
         // Fifth logged event for Metric 1.
         // Fifth logged event for Metric 2.
@@ -361,14 +395,15 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act2TtlSecs * 1000L);
+        RunUtil.getDefault().sleep(act2TtlSecs * 1000L);
 
         // Metric 1 event ignored.
         // Metric 2 event ignored.
         // Eighth logged event for Metric 3.
         logAllMetrics();
 
-        ConfigMetricsReportList reportList = getReportList();
+        ConfigMetricsReportList reportList = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
         assertThat(reports).hasSize(3);
 
@@ -387,27 +422,27 @@
 
     /**
      * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: ON_BOOT
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: IMMEDIATE
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: ON_BOOT
      *
      * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: IMMEDIATE
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: ON_BOOT
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: IMMEDIATE
      *
      * Metric 3: No activations; always active
      **/
     public void testMultipleActivations() throws Exception {
         final int act1TtlSecs = 200;
         final int act2TtlSecs = 400;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
+        ConfigUtils.uploadConfig(getDevice(), createConfig(act1TtlSecs, act2TtlSecs));
 
         // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
         // Time remaining:
@@ -415,8 +450,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -428,7 +464,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L / 2);
 
         // Second logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -441,8 +477,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Third logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -454,7 +491,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
 
         // Fourth logged event for Metric 1.
         // First logged event for Metric 2.
@@ -467,8 +504,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Fifth logged event for Metric 1.
         // Second logged event for Metric 2.
@@ -481,7 +519,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L);
 
         // Metric 1 event ignored.
         // Metric 2 event ignored.
@@ -493,15 +531,16 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
-        Thread.sleep(10_000L);
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
+        RunUtil.getDefault().sleep(10_000L);
 
         // Metric 1 event ignored.
         // Metric 2 event ignored.
         // Seventh logged event for Metric 3.
         logAllMetrics();
 
-        ConfigMetricsReportList reportList = getReportList();
+        ConfigMetricsReportList reportList = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
         assertThat(reports).hasSize(3);
 
@@ -518,35 +557,45 @@
         verifyMetrics(report, 0, 0, 1);
     }
 
+    /**
+     * Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
+     */
+    private List<ConfigMetricsReport> getSortedConfigMetricsReports(
+            ConfigMetricsReportList configMetricsReportList) {
+        return configMetricsReportList.getReportsList().stream().sorted(
+                Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos)).collect(
+                Collectors.toList());
+    }
+
     private void logAllMetrics() throws Exception {
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
-        doAppBreadcrumbReported(metric2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
-        doAppBreadcrumbReported(metric3MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric3MatcherId);
+        RunUtil.getDefault().sleep(10L);
     }
 
     private void verifyMetrics(ConfigMetricsReport report, int metric1Count, int metric2Count,
             int metric3Count) throws Exception {
         assertThat(report.getMetricsCount()).isEqualTo(3);
 
-        verifyMetric(
-                report.getMetrics(0),   // StatsLogReport
+        verifyMetric(report.getMetrics(0),   // StatsLogReport
                 1,                      // Metric Id
                 1,                      // Metric what atom matcher label
                 metric1Count            // Data count
         );
-        verifyMetric(
-                report.getMetrics(1),   // StatsLogReport
+        verifyMetric(report.getMetrics(1),   // StatsLogReport
                 2,                      // Metric Id
                 2,                      // Metric what atom matcher label
                 metric2Count            // Data count
         );
-        verifyMetric(
-                report.getMetrics(2),   // StatsLogReport
+        verifyMetric(report.getMetrics(2),   // StatsLogReport
                 3,                      // Metric Id
                 3,                      // Metric what atom matcher label
                 metric3Count            // Data count
@@ -562,7 +611,8 @@
         StatsLogReport.EventMetricDataWrapper eventData = metricReport.getEventMetrics();
         List<EventMetricData> eventMetricDataList = new ArrayList<>();
         for (EventMetricData eventMetricData : eventData.getDataList()) {
-            eventMetricDataList.addAll(backfillAggregatedAtomsInEventMetric(eventMetricData));
+            eventMetricDataList.addAll(
+                    ReportUtils.backfillAggregatedAtomsInEventMetric(eventMetricData));
         }
         assertThat(eventMetricDataList).hasSize(dataCount);
         for (EventMetricData eventMetricData : eventMetricDataList) {
diff --git a/tests/src/android/cts/statsd/metric/MetricsUtils.java b/tests/src/android/cts/statsd/metric/MetricsUtils.java
index 8f559d2..010df49 100644
--- a/tests/src/android/cts/statsd/metric/MetricsUtils.java
+++ b/tests/src/android/cts/statsd/metric/MetricsUtils.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.cts.statsd.atom.BufferDebug;
+
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventActivation;
@@ -24,11 +26,26 @@
 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.Message;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
 
 public class MetricsUtils {
+    public static final String DEVICE_SIDE_TEST_PACKAGE =
+            "com.android.server.cts.device.statsd";
+    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
     public static final long COUNT_METRIC_ID = 3333;
     public static final long DURATION_METRIC_ID = 4444;
     public static final long GAUGE_METRIC_ID = 5555;
@@ -38,20 +55,19 @@
     public static AtomMatcher.Builder getAtomMatcher(int atomId) {
         AtomMatcher.Builder builder = AtomMatcher.newBuilder();
         builder.setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(atomId));
+                .setAtomId(atomId));
         return builder;
     }
 
     public static AtomMatcher startAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                            .setEqInt(AppBreadcrumbReported.State.START.ordinal())))
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                .setEqInt(AppBreadcrumbReported.State.START.getNumber())))
+                .build();
     }
 
     public static AtomMatcher startAtomMatcherWithLabel(int id, int label) {
@@ -59,15 +75,14 @@
     }
 
     public static AtomMatcher stopAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                            .setEqInt(AppBreadcrumbReported.State.STOP.ordinal())))
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                .setEqInt(AppBreadcrumbReported.State.STOP.getNumber())))
+                .build();
     }
 
     public static AtomMatcher stopAtomMatcherWithLabel(int id, int label) {
@@ -81,16 +96,16 @@
                         .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
                                 .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                .setEqInt(AppBreadcrumbReported.State.UNSPECIFIED.ordinal())))
+                                .setEqInt(AppBreadcrumbReported.State.UNSPECIFIED.getNumber())))
                 .build();
     }
 
     public static AtomMatcher simpleAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder().setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
+                .build();
     }
 
     public static AtomMatcher appBreadcrumbMatcherWithLabel(int id, int label) {
@@ -113,7 +128,7 @@
                         .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
                                 .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                .setEqInt(state.ordinal()))
+                                .setEqInt(state.getNumber()))
                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
                                 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
                                 .setEqInt(label)))
@@ -121,16 +136,16 @@
     }
 
     public static AtomMatcher simpleAtomMatcher(int id, int label) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                            .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                            .setEqInt(label)
-                  )
-          )
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                .setEqInt(label)
+                        )
+                )
+                .build();
     }
 
     public static EventActivation.Builder createEventActivation(int ttlSecs, int matcherId,
@@ -142,7 +157,14 @@
     }
 
     public static long StringToId(String str) {
-      return str.hashCode();
+        return str.hashCode();
+    }
+
+    public static String getCurrentLogcatDate(ITestDevice device) throws Exception {
+        // TODO: Do something more robust than this for getting logcat markers.
+        long timestampMs = device.getDeviceDate();
+        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
+                .format(new Date(timestampMs));
     }
 
     public static void assertBucketTimePresent(Message bucketInfo) {
@@ -154,11 +176,60 @@
         if (bucketNum != null && bucketInfo.hasField(bucketNum)) {
             found = true;
         } else if (startMillis != null && bucketInfo.hasField(startMillis) &&
-                   endMillis != null && bucketInfo.hasField(endMillis)) {
+                endMillis != null && bucketInfo.hasField(endMillis)) {
             found = true;
         }
         assertWithMessage(
                 "Bucket info did not have either bucket num or start and end elapsed millis"
         ).that(found).isTrue();
     }
+
+    public static boolean didIncidentdFireSince(ITestDevice device, String date) throws Exception {
+        final String INCIDENTD_TAG = "incidentd";
+        final String INCIDENTD_STARTED_STRING = "reportIncident";
+        // TODO: Do something more robust than this in case of delayed logging.
+        RunUtil.getDefault().sleep(1000);
+        String log = getLogcatSince(device, date, String.format(
+                "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
+        return log.contains(INCIDENTD_STARTED_STRING);
+    }
+
+    public static String getLogcatSince(ITestDevice device, String date, String logcatParams)
+            throws Exception {
+        return device.executeShellCommand(String.format(
+                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
+    }
+
+    /**
+     * Call onto the device with an adb shell command and get the results of
+     * that as a proto of the given type.
+     *
+     * @param parser  A protobuf parser object. e.g. MyProto.parser()
+     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
+     * @throws DeviceNotAvailableException    If there was a problem communicating with
+     *                                        the test device.
+     * @throws InvalidProtocolBufferException If there was an error parsing
+     *                                        the proto. Note that a 0 length buffer is not
+     *                                        necessarily an error.
+     */
+    public static <T extends MessageLite> T getDump(ITestDevice device, Parser<T> parser,
+            String command)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        device.executeShellCommand(command, receiver);
+        if (false) {
+            LogUtil.CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
+                    + " for command: " + command + "\n"
+                    + BufferDebug.debugString(receiver.getOutput(), -1));
+        }
+        try {
+            return parser.parseFrom(receiver.getOutput());
+        } catch (Exception ex) {
+            LogUtil.CLog.d(
+                    "Error parsing " + parser.getClass().getCanonicalName() + " for command: "
+                            + command
+                            + BufferDebug.debugString(receiver.getOutput(), 16384));
+            throw ex;
+        }
+    }
 }
diff --git a/tests/src/android/cts/statsd/metric/ValueMetricsTests.java b/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
index 3b4ef56..4ac1d5e 100644
--- a/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
@@ -17,14 +17,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto.ActivationType;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
 import com.android.internal.os.StatsdConfigProto.MetricActivation;
 import com.android.internal.os.StatsdConfigProto.Predicate;
 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
@@ -41,286 +41,320 @@
 import com.android.os.StatsLog.ValueMetricData;
 
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
 
-public class ValueMetricsTests extends DeviceAtomTestCase {
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
-  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
+import com.google.protobuf.ExtensionRegistry;
 
-  public void testValueMetric() throws Exception {
-    // Add AtomMatcher's.
-    AtomMatcher startAtomMatcher =
-        MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
-    AtomMatcher stopAtomMatcher =
-        MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
-    AtomMatcher atomMatcher =
-        MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+public class ValueMetricsTests extends DeviceTestCase {
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
+    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
 
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addAtomMatcher(atomMatcher);
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-        ValueMetric.newBuilder()
-            .setId(MetricsUtils.VALUE_METRIC_ID)
-            .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-            .setBucket(TimeUnit.CTS)
-            .setValueField(FieldMatcher.newBuilder()
-                               .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                               .addChild(FieldMatcher.newBuilder().setField(
-                                   AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
-            .setDimensionsInWhat(FieldMatcher.newBuilder()
-                                     .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                     .build())
-            .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    // Create AppBreadcrumbReported Start/Stop events.
-    doAppBreadcrumbReportedStart(1);
-    Thread.sleep(1000);
-    doAppBreadcrumbReportedStop(1);
-    doAppBreadcrumbReportedStart(3);
-    doAppBreadcrumbReportedStop(3);
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    assertThat(bucketCount).isGreaterThan(1);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
-    }
-    assertThat(totalValue).isEqualTo(8);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testPullerAcrossBuckets() throws Exception {
-    // Add AtomMatcher's.
-    final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
-    final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
-    final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
-    AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
-    AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addPredicate(Predicate.newBuilder()
-            .setId(predicateName.hashCode())
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                    .setStart(predicateTrueName.hashCode())
-                    .setStop(predicateFalseName.hashCode())
-                    .setCountNesting(false)
-            )
-    );
-
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(AtomMatcher.newBuilder()
-            .setId(atomName.hashCode())
-            .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .setCondition(predicateName.hashCode())
-                    .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    // Create AppBreadcrumbReported Start/Stop events.
-    doAppBreadcrumbReportedStart(1);
-    // Wait for 2 min and 1 sec to capture at least 2 buckets
-    Thread.sleep(2*60_000 + 10_000);
-    doAppBreadcrumbReportedStop(1);
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    // should have at least 2 buckets
-    assertThat(bucketCount).isAtLeast(2);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
-    }
-    // At most we lose one full min bucket
-    assertThat(totalValue).isGreaterThan(130_000 - 60_000);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testMultipleEventsPerBucket() throws Exception {
-    // Add AtomMatcher's.
-    final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
-    final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
-    final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
-    AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
-    AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addPredicate(Predicate.newBuilder()
-            .setId(predicateName.hashCode())
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                    .setStart(predicateTrueName.hashCode())
-                    .setStop(predicateFalseName.hashCode())
-                    .setCountNesting(false)
-            )
-    );
-
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(AtomMatcher.newBuilder()
-            .setId(atomName.hashCode())
-            .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .setCondition(predicateName.hashCode())
-                    .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    final int NUM_EVENTS = 10;
-    final long GAP_INTERVAL = 10_000;
-    // Create AppBreadcrumbReported Start/Stop events.
-    for (int i = 0; i < NUM_EVENTS; i ++) {
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(GAP_INTERVAL);
-      doAppBreadcrumbReportedStop(1);
-      Thread.sleep(GAP_INTERVAL);
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
     }
 
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    // should have at least 2 buckets
-    assertThat(bucketCount).isAtLeast(2);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
     }
-    // At most we lose one full min bucket
-    assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testPullerAcrossBucketsWithActivation() throws Exception {
-    StatsdConfig.Builder builder = createConfigBuilder();
-
-    // Add AtomMatcher's.
-    int activationAtomMatcherId = 1;
-    int activationAtomMatcherLabel = 1;
-    AtomMatcher activationAtomMatcher =
-            MetricsUtils.appBreadcrumbMatcherWithLabel(
-                    activationAtomMatcherId, activationAtomMatcherLabel);
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder()
-            .setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(activationAtomMatcher)
-            .addAtomMatcher(AtomMatcher.newBuilder()
-                    .setId(atomName.hashCode())
-                    .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .build());
-    // Add activation.
-    builder.addMetricActivation(MetricActivation.newBuilder()
-          .setMetricId(MetricsUtils.VALUE_METRIC_ID)
-          .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-          .addEventActivation(EventActivation.newBuilder()
-                  .setAtomMatcherId(activationAtomMatcherId)
-                  .setTtlSeconds(5)));
 
 
-    // Upload config.
-    uploadConfig(builder);
+    public void testValueMetric() throws Exception {
+        // Add AtomMatcher's.
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+        AtomMatcher atomMatcher =
+                MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
 
-    // Wait for 1 min and 10 sec to capture at least 1 bucket
-    Thread.sleep(60_000 + 10_000);
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addAtomMatcher(atomMatcher);
 
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                .setBucket(TimeUnit.CTS)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
+                .setDimensionsInWhat(FieldMatcher.newBuilder()
+                        .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                        .build())
+                .build());
 
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
-    // Skipped buckets are not added when metric is empty.
-    assertThat(metricReport.getValueMetrics().getSkippedList()).isEmpty();
-  }
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Create AppBreadcrumbReported Start/Stop events.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(1000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 3);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 3);
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.hasValueMetrics()).isTrue();
+        StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
+        assertThat(valueData.getDataCount()).isEqualTo(1);
+
+        int bucketCount = valueData.getData(0).getBucketInfoCount();
+        assertThat(bucketCount).isGreaterThan(1);
+        ValueMetricData data = valueData.getData(0);
+        int totalValue = 0;
+        for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
+            MetricsUtils.assertBucketTimePresent(bucketInfo);
+            assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+            assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
+            totalValue += (int) bucketInfo.getValues(0).getValueLong();
+        }
+        assertThat(totalValue).isEqualTo(8);
+    }
+
+    // Test value metric with pulled atoms and across multiple buckets
+    public void testPullerAcrossBuckets() throws Exception {
+        // Add AtomMatcher's.
+        final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
+        final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
+        final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
+
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
+
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addPredicate(Predicate.newBuilder()
+                .setId(predicateName.hashCode())
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(predicateTrueName.hashCode())
+                        .setStop(predicateFalseName.hashCode())
+                        .setCountNesting(false)
+                )
+        );
+
+        final String atomName = "SYSTEM_ELAPSED_REALTIME";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(
+                Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
+        builder.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(atomName.hashCode())
+                .setBucket(TimeUnit.ONE_MINUTE)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
+                .setCondition(predicateName.hashCode())
+                .build());
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Create AppBreadcrumbReported Start/Stop events.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        // Wait for 2 min and 1 sec to capture at least 2 buckets
+        RunUtil.getDefault().sleep(2 * 60_000 + 10_000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.hasValueMetrics()).isTrue();
+        StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
+        assertThat(valueData.getDataCount()).isEqualTo(1);
+
+        int bucketCount = valueData.getData(0).getBucketInfoCount();
+        // should have at least 2 buckets
+        assertThat(bucketCount).isAtLeast(2);
+        ValueMetricData data = valueData.getData(0);
+        int totalValue = 0;
+        for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
+            MetricsUtils.assertBucketTimePresent(bucketInfo);
+            assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+            assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
+            totalValue += (int) bucketInfo.getValues(0).getValueLong();
+        }
+        // At most we lose one full min bucket
+        assertThat(totalValue).isGreaterThan(130_000 - 60_000);
+    }
+
+    // Test value metric with pulled atoms and across multiple buckets
+    public void testMultipleEventsPerBucket() throws Exception {
+        // Add AtomMatcher's.
+        final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
+        final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
+        final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
+
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
+
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addPredicate(Predicate.newBuilder()
+                .setId(predicateName.hashCode())
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(predicateTrueName.hashCode())
+                        .setStop(predicateFalseName.hashCode())
+                        .setCountNesting(false)
+                )
+        );
+
+        final String atomName = "SYSTEM_ELAPSED_REALTIME";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(
+                Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
+        builder.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(atomName.hashCode())
+                .setBucket(TimeUnit.ONE_MINUTE)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
+                .setCondition(predicateName.hashCode())
+                .build());
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        final int NUM_EVENTS = 10;
+        final long GAP_INTERVAL = 10_000;
+        // Create AppBreadcrumbReported Start/Stop events.
+        for (int i = 0; i < NUM_EVENTS; i++) {
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AppBreadcrumbReported.State.START.getNumber(), 1);
+            RunUtil.getDefault().sleep(GAP_INTERVAL);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AppBreadcrumbReported.State.STOP.getNumber(), 1);
+            RunUtil.getDefault().sleep(GAP_INTERVAL);
+        }
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.hasValueMetrics()).isTrue();
+        StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
+        assertThat(valueData.getDataCount()).isEqualTo(1);
+
+        int bucketCount = valueData.getData(0).getBucketInfoCount();
+        // should have at least 2 buckets
+        assertThat(bucketCount).isAtLeast(2);
+        ValueMetricData data = valueData.getData(0);
+        int totalValue = 0;
+        for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
+            MetricsUtils.assertBucketTimePresent(bucketInfo);
+            assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+            assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
+            totalValue += (int) bucketInfo.getValues(0).getValueLong();
+        }
+        // At most we lose one full min bucket
+        assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
+    }
+
+    // Test value metric with pulled atoms and across multiple buckets
+    public void testPullerAcrossBucketsWithActivation() throws Exception {
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+
+        // Add AtomMatcher's.
+        int activationAtomMatcherId = 1;
+        int activationAtomMatcherLabel = 1;
+        AtomMatcher activationAtomMatcher =
+                MetricsUtils.appBreadcrumbMatcherWithLabel(
+                        activationAtomMatcherId, activationAtomMatcherLabel);
+        final String atomName = "SYSTEM_ELAPSED_REALTIME";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder()
+                .setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
+        builder.addAtomMatcher(activationAtomMatcher)
+                .addAtomMatcher(AtomMatcher.newBuilder()
+                        .setId(atomName.hashCode())
+                        .setSimpleAtomMatcher(sam));
+
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(atomName.hashCode())
+                .setBucket(TimeUnit.ONE_MINUTE)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
+                .build());
+        // Add activation.
+        builder.addMetricActivation(MetricActivation.newBuilder()
+                .setMetricId(MetricsUtils.VALUE_METRIC_ID)
+                .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                .addEventActivation(EventActivation.newBuilder()
+                        .setAtomMatcherId(activationAtomMatcherId)
+                        .setTtlSeconds(5)));
+
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Wait for 1 min and 10 sec to capture at least 1 bucket
+        RunUtil.getDefault().sleep(60_000 + 10_000);
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
+        // Skipped buckets are not added when metric is empty.
+        assertThat(metricReport.getValueMetrics().getSkippedList()).isEmpty();
+    }
 
     public void testValueMetricWithConditionAndActivation() throws Exception {
         final int conditionLabel = 2;
@@ -340,7 +374,8 @@
         AtomMatcher whatMatcher =
                 MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
 
-        StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(conditionStartAtomMatcher)
                 .addAtomMatcher(conditionStopAtomMatcher)
                 .addAtomMatcher(whatMatcher)
@@ -352,14 +387,13 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add ValueMetric.
-        builder
-                .addValueMetric(ValueMetric.newBuilder()
+        builder.addValueMetric(ValueMetric.newBuilder()
                         .setId(MetricsUtils.VALUE_METRIC_ID)
                         .setWhat(whatMatcher.getId())
                         .setBucket(TimeUnit.ONE_MINUTE)
@@ -380,66 +414,79 @@
                         )
                 );
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Skipped due to unknown condition at start of bucket.
-        doAppBreadcrumbReported(10);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 10);
+        RunUtil.getDefault().sleep(10);
 
         // Skipped due to unknown condition at start of bucket.
-        doAppBreadcrumbReported(200);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 200);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should not be counted because condition is false.
-        doAppBreadcrumbReported(3_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 3_000);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(40_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 40_000);
+        RunUtil.getDefault().sleep(10);
 
         // Condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Event should not be counted, metric is still not active.
-        doAppBreadcrumbReported(500_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 500_000);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         //  Log an event that should be counted.
-        doAppBreadcrumbReported(6_000_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 6_000_000);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(70_000_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 70_000_000);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
         assertThat(metricReport.hasValueMetrics()).isTrue();
diff --git a/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java b/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
index 5191f39..8e563b9 100644
--- a/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
+++ b/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
@@ -1,14 +1,44 @@
 package android.cts.statsd.restricted;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 /**
  * Tests Suite for restricted stats permissions.
  */
-public class ReadRestrictedStatsPermissionTest extends DeviceAtomTestCase {
+public class ReadRestrictedStatsPermissionTest extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     public void testReadRestrictedStatsPermission() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".RestrictedPermissionTests", "testReadRestrictedStatsPermission");
     }
 }
diff --git a/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
index d3c142b..a79b2b0 100644
--- a/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
+++ b/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
@@ -16,12 +16,11 @@
 package android.cts.statsd.subscriber;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import com.android.compatibility.common.util.CpuFeatures;
 import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.SystemUptime;
 import com.android.os.ShellConfig;
 import com.android.os.statsd.ShellDataProto;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
@@ -29,6 +28,8 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
+
 import com.google.common.io.Files;
 import com.google.protobuf.InvalidProtocolBufferException;
 
@@ -37,18 +38,20 @@
 import java.nio.ByteOrder;
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
-import android.cts.statsd.atom.AtomTestCase;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
 
 /**
  * Statsd shell data subscription test.
  */
-public class ShellSubscriberTest extends AtomTestCase {
+public class ShellSubscriberTest extends DeviceTestCase {
     private int sizetBytes;
 
     public class ShellSubscriptionThread extends Thread {
         String cmd;
         CollectingByteOutputReceiver receiver;
         int maxTimeoutForCommandSec;
+
         public ShellSubscriptionThread(
                 String cmd,
                 CollectingByteOutputReceiver receiver,
@@ -57,7 +60,8 @@
             this.receiver = receiver;
             this.maxTimeoutForCommandSec = maxTimeoutForCommandSec;
         }
-        public void run () {
+
+        public void run() {
             try {
                 getDevice().executeShellCommand(cmd, receiver, maxTimeoutForCommandSec,
                         /*maxTimeToOutputShellResponse=*/maxTimeoutForCommandSec, TimeUnit.SECONDS,
@@ -124,27 +128,28 @@
 
             String cmd = "cat " + remotePath + " |  cmd stats data-subscribe " + timeout;
             String firstSubCmd =
-                        "cat " + remotePath + " |  cmd stats data-subscribe " + firstSubTimeout;
+                    "cat " + remotePath + " |  cmd stats data-subscribe " + firstSubTimeout;
 
             for (int i = 0; i < maxSubs; i++) {
                 // Run data-subscribe on a thread
                 receivers[i] = new CollectingByteOutputReceiver();
                 if (i == 0) {
                     shellThreads[i] =
-                        new ShellSubscriptionThread(firstSubCmd, receivers[i], firstSubTimeout);
+                            new ShellSubscriptionThread(firstSubCmd, receivers[i], firstSubTimeout);
                 } else {
                     shellThreads[i] =
-                        new ShellSubscriptionThread(cmd, receivers[i], timeout);
+                            new ShellSubscriptionThread(cmd, receivers[i], timeout);
                 }
                 shellThreads[i].start();
                 LogUtil.CLog.d("Starting new shell subscription.");
             }
             // Sleep 2 seconds to make sure all subscription clients are initialized before
             // first pushed event
-            Thread.sleep(2000);
+            RunUtil.getDefault().sleep(2000);
 
             // Pushed event. arbitrary label = 1
-            doAppBreadcrumbReported(1);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 1);
 
             // Make sure the last 19 threads die before moving to the next step.
             // First subscription is still active due to its longer timeout that is used keep
@@ -163,26 +168,27 @@
                 // Run data-subscribe on a thread
                 receivers[i] = new CollectingByteOutputReceiver();
                 shellThreads[i] =
-                    new ShellSubscriptionThread(cmd, receivers[i], timeout);
+                        new ShellSubscriptionThread(cmd, receivers[i], timeout);
                 shellThreads[i].start();
                 LogUtil.CLog.d("Starting new shell subscription.");
             }
             // Sleep 2 seconds to make sure all subscription clients are initialized before
             // pushed event
-            Thread.sleep(2000);
+            RunUtil.getDefault().sleep(2000);
 
             // ShellSubscriber only allows 20 subscriptions at a time. This is the 21st which will
             // be ignored
             receivers[maxSubs] = new CollectingByteOutputReceiver();
             shellThreads[maxSubs] =
-                new ShellSubscriptionThread(cmd, receivers[maxSubs], timeout);
+                    new ShellSubscriptionThread(cmd, receivers[maxSubs], timeout);
             shellThreads[maxSubs].start();
 
             // Sleep 1 seconds to ensure that the 21st subscription is rejected
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(1000);
 
             // Pushed event. arbitrary label = 1
-            doAppBreadcrumbReported(1);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 1);
 
             // Make sure all the threads die before moving to the next step
             for (int i = 0; i <= maxSubs; i++) {
@@ -219,7 +225,7 @@
     private ShellConfig.ShellSubscription createConfig() {
         return ShellConfig.ShellSubscription.newBuilder()
                 .addPushed((StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
                         .build()).build();
     }
 
@@ -260,15 +266,16 @@
             String cmd = "cat " + remotePath + " |  cmd stats data-subscribe " + timeout;
             // Run data-subscribe on a thread
             ShellSubscriptionThread shellThread =
-                                    new ShellSubscriptionThread(cmd, receiver, timeout);
+                    new ShellSubscriptionThread(cmd, receiver, timeout);
             shellThread.start();
             LogUtil.CLog.d("Starting new shell subscription.");
 
             // Sleep a second to make sure subscription is initiated
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(1000);
 
             // Pushed event. arbitrary label = 1
-            doAppBreadcrumbReported(1);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 1);
             // Wait for thread to die before returning
             shellThread.join();
             // Remove config from device if not already deleted
@@ -319,7 +326,7 @@
             assertThat(data.getAtom(0).hasAppBreadcrumbReported()).isTrue();
             assertThat(data.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(1);
             assertThat(data.getAtom(0).getAppBreadcrumbReported().getState().getNumber())
-                       .isEqualTo(1);
+                    .isEqualTo(1);
             atomCount++;
             startIndex += sizetBytes + dataLength;
         }
diff --git a/tests/src/android/cts/statsd/uidmap/UidMapTests.java b/tests/src/android/cts/statsd/uidmap/UidMapTests.java
index 4ceefa7..595a987 100644
--- a/tests/src/android/cts/statsd/uidmap/UidMapTests.java
+++ b/tests/src/android/cts/statsd/uidmap/UidMapTests.java
@@ -17,7 +17,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.internal.os.StatsdConfigProto;
 import com.android.os.AtomsProto;
@@ -25,18 +30,54 @@
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.UidMapping;
 import com.android.os.StatsLog.UidMapping.PackageInfoSnapshot;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-import java.util.List;
+import com.google.protobuf.ExtensionRegistry;
 
-public class UidMapTests extends DeviceAtomTestCase {
+public class UidMapTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final long DEVICE_SIDE_TEST_PKG_HASH =
+            Long.parseUnsignedLong("15694052924544098582");
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     // Tests that every report has at least one snapshot.
     public void testUidSnapshotIncluded() throws Exception {
         // There should be at least the test app installed during the test setup.
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         for (ConfigMetricsReport report : reports.getReportsList()) {
@@ -64,21 +105,24 @@
 
     // Tests that delta event included during app installation.
     public void testChangeFromInstallation() throws Exception {
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        getDevice().uninstallPackage(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
         // Install the package after the config is sent to statsd. The uid map is not guaranteed to
         // be updated if there's no config in statsd.
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
         final String result = getDevice().installPackage(
-                buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
+                buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK), false, true);
 
-        Thread.sleep(WAIT_TIME_LONG);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
-        int uid = getUid();
+        int uid = DeviceUtils.getAppUid(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         for (ConfigMetricsReport report : reports.getReportsList()) {
             LogUtil.CLog.d("Got the following report: \n" + report.toString());
             if (hasMatchingChange(report.getUidMap(), uid, false)) {
@@ -91,18 +135,23 @@
     // We check that a re-installation gives a change event (similar to an app upgrade).
     public void testChangeFromReinstall() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        getDevice().installPackage(buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK),
+                false, true);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
         // Now enable re-installation.
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
+        getDevice().installPackage(buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK), true,
+                true);
 
-        Thread.sleep(WAIT_TIME_LONG);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
-        int uid = getUid();
+        int uid = DeviceUtils.getAppUid(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         for (ConfigMetricsReport report : reports.getReportsList()) {
             LogUtil.CLog.d("Got the following report: \n" + report.toString());
             if (hasMatchingChange(report.getUidMap(), uid, false)) {
@@ -114,14 +163,19 @@
 
     public void testChangeFromUninstall() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
-        int uid = getUid();
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        getDevice().installPackage(buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK), true,
+                true);
 
-        Thread.sleep(WAIT_TIME_LONG);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        int uid = DeviceUtils.getAppUid(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        getDevice().uninstallPackage(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
 
-        ConfigMetricsReportList reports = getReportList();
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
diff --git a/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java b/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java
index 37ded0b..4fa5e2a 100644
--- a/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java
+++ b/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java
@@ -1,34 +1,64 @@
 package android.cts.statsd.validation;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 /**
  * Tests Suite for directories used by Statsd.
  */
-public class DirectoryValidationTest extends DeviceAtomTestCase {
+public class DirectoryValidationTest extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     public void testStatsActiveMetricDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsActiveMetricDirectoryExists");
     }
 
     public void testStatsDataDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsDataDirectoryExists");
     }
 
     public void testStatsMetadataDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsMetadataDirectoryExists");
     }
 
     public void testStatsServiceDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsServiceDirectoryExists");
     }
 
     public void testTrainInfoDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testTrainInfoDirectoryExists");
     }
 }
diff --git a/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java
index 1ca4c5c..6b61497 100644
--- a/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ b/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -18,20 +18,22 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.cts.statsd.atom.ProcStateTestCase;
-import android.service.procstats.ProcessState;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 import android.service.procstats.AggregatedProcessState;
 
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.ProcessStateAggregated;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
 import com.android.os.AtomsProto.ProcessStatsProto;
 import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.ValueBucketInfo;
-import com.android.os.StatsLog.ValueMetricData;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.RunUtil;
 
 import java.util.List;
 
@@ -42,54 +44,61 @@
 
     private static final String TAG = "Statsd.ProcStatsValidationTests";
 
+    private static final String sBackgroundServiceName = "StatsdCtsBackgroundService";
+
     private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
 
+    private static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
+
     private void toggleScreenAndSleep(final long duration) throws Exception {
         final long half = duration >> 1;
-        Thread.sleep(half);
-        turnScreenOff();
-        Thread.sleep(half);
-        turnScreenOn();
+        RunUtil.getDefault().sleep(half);
+        DeviceUtils.turnScreenOff(getDevice());
+        RunUtil.getDefault().sleep(half);
+        DeviceUtils.turnScreenOn(getDevice());
     }
 
     public void testProcessStateByPulling() throws Exception {
         startProcStatsTesting();
         clearProcStats();
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         // foreground service
         executeForegroundService();
-        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        RunUtil.getDefault().sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
         // background
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        DeviceUtils.executeBackgroundService(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                sBackgroundServiceName, ACTION_BACKGROUND_SLEEP);
+        RunUtil.getDefault().sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
         // top
         executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        RunUtil.getDefault().sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
         // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        DeviceUtils.executeBackgroundService(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                sBackgroundServiceName, ACTION_END_IMMEDIATELY);
         final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
+        RunUtil.getDefault().sleep(cacheTime);
         // foreground
         // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
         executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+        RunUtil.getDefault().sleep(EXTRA_WAIT_TIME_MS + 5_000);
 
-        Thread.sleep(60_000);
-        uninstallPackage();
+        RunUtil.getDefault().sleep(60_000);
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         stopProcStatsTesting();
         commitProcStatsToDisk();
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         final String fileName = "PROCSTATSQ_PULL.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        StatsdConfig config = ValidationTestUtil.getConfig(fileName, mCtsBuild);
         LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT + 5_000);
+        ConfigUtils.uploadConfig(getDevice(), config.toBuilder());
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AtomsProto.AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT + 5_000);
 
-        List<Atom> statsdData = getGaugeMetricDataList();
+        List<Atom> statsdData = ReportUtils.getGaugeMetricAtoms(getDevice());
 
         List<android.service.procstats.ProcessStatsProto> processStatsProtoList
                 = getAllProcStatsProtoForStatsd();
@@ -101,7 +110,8 @@
         String statsdPkgName = "com.android.server.cts.device.statsd";
         long rssAvgStatsd = 0;
         for (Atom d : statsdData) {
-            for (ProcessStatsProto proc : d.getProcStats().getProcStatsSection().getProcessStatsList()) {
+            for (ProcessStatsProto proc :
+                    d.getProcStats().getProcStatsSection().getProcessStatsList()) {
                 if (proc.getProcess().equals(statsdPkgName)) {
                     LogUtil.CLog.d("Got proto from statsd:");
                     LogUtil.CLog.d(proc.toString());
@@ -116,7 +126,7 @@
         }
 
         long rssAvgProcstats = 0;
-        for (android.service.procstats.ProcessStatsProto process: processStatsProtoList) {
+        for (android.service.procstats.ProcessStatsProto process : processStatsProtoList) {
             if (process.getProcess().equals(statsdPkgName)) {
                 LogUtil.CLog.d("Got proto from procstats dumpsys:");
                 LogUtil.CLog.d(process.toString());
@@ -139,111 +149,112 @@
          * Temporarily disable this test as the proc stats data being pulled into the statsd
          * doesn't include the pkg part now.
          *
-        startProcStatsTesting();
-        clearProcStats();
-        Thread.sleep(WAIT_TIME_SHORT);
+         startProcStatsTesting();
+         clearProcStats();
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
 
-        // foreground service
-        executeForegroundService();
-        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
-        // background
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
-        // top
-        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
-        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
-        final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
-        // foreground
-        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
-        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+         // foreground service
+         executeForegroundService();
+         RunUtil.getDefault().sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+         // background
+         executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+         RunUtil.getDefault().sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+         // top
+         executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+         RunUtil.getDefault().sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+         // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+         executeBackgroundService(ACTION_END_IMMEDIATELY);
+         final int cacheTime = 2_000; // process should be in cached state for up to this long
+         RunUtil.getDefault().sleep(cacheTime);
+         // foreground
+         // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
+         executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+         RunUtil.getDefault().sleep(EXTRA_WAIT_TIME_MS + 5_000);
 
-        Thread.sleep(60_000);
-        uninstallPackage();
-        stopProcStatsTesting();
-        commitProcStatsToDisk();
-        Thread.sleep(WAIT_TIME_SHORT);
+         RunUtil.getDefault().sleep(60_000);
+         uninstallPackage();
+         stopProcStatsTesting();
+         commitProcStatsToDisk();
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
 
-        final String fileName = "PROCSTATSQ_PULL_PKG_PROC.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
+         final String fileName = "PROCSTATSQ_PULL_PKG_PROC.pbtxt";
+         StatsdConfig config = createValidationUtil().getConfig(fileName);
+         LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+         uploadConfig(config);
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
+         setAppBreadcrumbPredicate();
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
 
-        List<Atom> statsdData = getGaugeMetricDataList();
-        assertThat(statsdData).isNotEmpty();
-        assertThat(
-                statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
-                        .getProcessStatsList()
-        ).isNotEmpty();
+         List<Atom> statsdData = getGaugeMetricDataList();
+         assertThat(statsdData).isNotEmpty();
+         assertThat(
+         statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
+         .getProcessStatsList()
+         ).isNotEmpty();
 
-        // We pull directly from ProcessStatsService, so not necessary to compare every field.
-        // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
-        LogUtil.CLog.d("======================");
+         // We pull directly from ProcessStatsService, so not necessary to compare every field.
+         // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
+         LogUtil.CLog.d("======================");
 
-        String statsdPkgName = "com.android.server.cts.device.statsd";
-        long rssAvgStatsd = 0;
-        long durationStatsd = 0;
-        for (Atom d : statsdData) {
-            for (ProcessStatsPackageProto pkg : d.getProcStatsPkgProc().getProcStatsSection().getPackageStatsList()) {
-                if (pkg.getPackage().equals(statsdPkgName)) {
-                    LogUtil.CLog.d("Got proto from statsd:");
-                    LogUtil.CLog.d(pkg.toString());
-                    for (ProcessStatsProto process : pkg.getProcessStatsList()) {
-                        for (ProcessStatsStateProto state : process.getStatesList()) {
-                            if (state.getProcessState()
-                                    == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                                durationStatsd = state.getDurationMillis();
-                                rssAvgStatsd = state.getRss().getAverage();
-                            }
-                        }
-                    }
-                }
-                assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
-                assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
-            }
-        }
+         String statsdPkgName = "com.android.server.cts.device.statsd";
+         long rssAvgStatsd = 0;
+         long durationStatsd = 0;
+         for (Atom d : statsdData) {
+         for (ProcessStatsPackageProto pkg : d.getProcStatsPkgProc().getProcStatsSection()
+         .getPackageStatsList()) {
+         if (pkg.getPackage().equals(statsdPkgName)) {
+         LogUtil.CLog.d("Got proto from statsd:");
+         LogUtil.CLog.d(pkg.toString());
+         for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+         for (ProcessStatsStateProto state : process.getStatesList()) {
+         if (state.getProcessState()
+         == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+         durationStatsd = state.getDurationMillis();
+         rssAvgStatsd = state.getRss().getAverage();
+         }
+         }
+         }
+         }
+         assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
+         assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
+         }
+         }
 
-        LogUtil.CLog.d("avg rss from statsd is " + rssAvgStatsd);
+         LogUtil.CLog.d("avg rss from statsd is " + rssAvgStatsd);
 
-        List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
+         List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
 
-        long pssAvgProcstats = 0;
-        long ussAvgProcstats = 0;
-        long rssAvgProcstats = 0;
-        long durationProcstats = 0;
-        int serviceStatsCount = 0;
-        int associationStatsCount = 0;
-        for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
-            if (pkg.getPackage().equals(statsdPkgName)) {
-                LogUtil.CLog.d("Got proto from procstats dumpsys:");
-                LogUtil.CLog.d(pkg.toString());
-                for (ProcessStatsProto process : pkg.getProcessStatsList()) {
-                    for (ProcessStatsStateProto state : process.getStatesList()) {
-                        if (state.getProcessState()
-                                == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                            durationProcstats = state.getDurationMillis();
-                            pssAvgProcstats = state.getPss().getAverage();
-                            ussAvgProcstats = state.getUss().getAverage();
-                            rssAvgProcstats = state.getRss().getAverage();
-                        }
-                    }
-                }
-            }
-            serviceStatsCount += pkg.getServiceStatsCount();
-            associationStatsCount += pkg.getAssociationStatsCount();
-        }
-        assertThat(serviceStatsCount).isGreaterThan(0);
-        assertThat(associationStatsCount).isGreaterThan(0);
+         long pssAvgProcstats = 0;
+         long ussAvgProcstats = 0;
+         long rssAvgProcstats = 0;
+         long durationProcstats = 0;
+         int serviceStatsCount = 0;
+         int associationStatsCount = 0;
+         for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
+         if (pkg.getPackage().equals(statsdPkgName)) {
+         LogUtil.CLog.d("Got proto from procstats dumpsys:");
+         LogUtil.CLog.d(pkg.toString());
+         for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+         for (ProcessStatsStateProto state : process.getStatesList()) {
+         if (state.getProcessState()
+         == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+         durationProcstats = state.getDurationMillis();
+         pssAvgProcstats = state.getPss().getAverage();
+         ussAvgProcstats = state.getUss().getAverage();
+         rssAvgProcstats = state.getRss().getAverage();
+         }
+         }
+         }
+         }
+         serviceStatsCount += pkg.getServiceStatsCount();
+         associationStatsCount += pkg.getAssociationStatsCount();
+         }
+         assertThat(serviceStatsCount).isGreaterThan(0);
+         assertThat(associationStatsCount).isGreaterThan(0);
 
-        LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
-        assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
-        */
+         LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
+         assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
+         */
     }
 
     private boolean isPssProfilingDisabled() throws Exception {
@@ -254,4 +265,45 @@
         final String dumpsys = device.executeShellCommand("dumpsys activity settings");
         return (dumpsys.contains(stringToCompare));
     }
+
+    protected void clearProcStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --clear");
+    }
+
+    private void startProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --start-testing");
+    }
+
+    private void stopProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
+    }
+
+    private void commitProcStatsToDisk() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --commit");
+    }
+
+    /*
+     * Get all processes' procstats statsd data in proto
+     */
+    protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
+            throws Exception {
+        try {
+            android.service.procstats.ProcessStatsSectionProto sectionProto = MetricsUtils.getDump(
+                    getDevice(),
+                    android.service.procstats.ProcessStatsSectionProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--statsd"));
+            List<android.service.procstats.ProcessStatsProto> processStatsProtoList
+                    = sectionProto.getProcessStatsList();
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (android.service.procstats.ProcessStatsProto processStatsProto
+                    : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
 }
diff --git a/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java b/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java
index 0ea332e..2a67748 100644
--- a/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java
+++ b/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java
@@ -16,13 +16,43 @@
 
 package android.cts.statsd.validation;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import static com.google.common.truth.Truth.assertThat;
 
-public class StatsFrameworkInitializerTest extends DeviceAtomTestCase {
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
+
+public class StatsFrameworkInitializerTest extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     public void testStatsFrameworkInitializer_failsWhenCalledOutsideOfSystemServiceRegistry()
             throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".StatsFrameworkInitializerTests", "testRegisterServiceWrappers_expectFail");
     }
 
diff --git a/tests/src/android/cts/statsd/validation/ValidationTestUtil.java b/tests/src/android/cts/statsd/validation/ValidationTestUtil.java
index d3e5bad..cb3a41e 100644
--- a/tests/src/android/cts/statsd/validation/ValidationTestUtil.java
+++ b/tests/src/android/cts/statsd/validation/ValidationTestUtil.java
@@ -15,11 +15,10 @@
  */
 package android.cts.statsd.validation;
 
-import android.cts.statsd.atom.BaseTestCase;
-
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.FileUtil;
 
 import com.google.protobuf.TextFormat;
@@ -28,14 +27,14 @@
 import java.io.File;
 import java.io.IOException;
 
-public class ValidationTestUtil extends BaseTestCase {
+public class ValidationTestUtil {
 
     private static final String TAG = "Statsd.ValidationTestUtil";
 
-    public StatsdConfig getConfig(String fileName) throws IOException {
+    public static StatsdConfig getConfig(String fileName, IBuildInfo ctsBuild) throws IOException {
         try {
             // TODO: Ideally, we should use real metrics that are also pushed to the fleet.
-            File configFile = getBuildHelper().getTestFile(fileName);
+            File configFile = getBuildHelper(ctsBuild).getTestFile(fileName);
             String configStr = FileUtil.readStringFromFile(configFile);
             StatsdConfig.Builder builder = StatsdConfig.newBuilder();
             TextFormat.merge(configStr, builder);
@@ -47,4 +46,8 @@
         }
         return null;
     }
+
+    private static CompatibilityBuildHelper getBuildHelper(IBuildInfo ctsBuild) {
+        return new CompatibilityBuildHelper(ctsBuild);
+    }
 }
diff --git a/tests/src/android/cts/statsd/validation/ValidationTests.java b/tests/src/android/cts/statsd/validation/ValidationTests.java
index 3add66d..a3f0109 100644
--- a/tests/src/android/cts/statsd/validation/ValidationTests.java
+++ b/tests/src/android/cts/statsd/validation/ValidationTests.java
@@ -18,7 +18,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 import android.os.BatteryPluggedStateEnum;
 import android.os.BatteryStatsProto;
 import android.os.UidProto;
@@ -47,9 +51,14 @@
 import com.android.os.StatsLog.DurationMetricData;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 import com.google.common.collect.Range;
+import com.google.protobuf.ExtensionRegistry;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -60,36 +69,53 @@
 /**
  * Side-by-side comparison between statsd and batterystats.
  */
-public class ValidationTests extends DeviceAtomTestCase {
+public class ValidationTests extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+        DeviceUtils.turnBatteryStatsAutoResetOff(
+                getDevice()); // Turn off Battery Stats auto resetting
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        DeviceUtils.resetBatteryStatus(getDevice());
+        DeviceUtils.turnScreenOn(getDevice());
+        DeviceUtils.turnBatteryStatsAutoResetOn(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     private static final String TAG = "Statsd.ValidationTests";
     private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
     private static final boolean ENABLE_LOAD_TEST = false;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        turnBatteryStatsAutoResetOff(); // Turn off Battery Stats auto resetting
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        resetBatteryStatus(); // Undo any unplugDevice().
-        turnScreenOn(); // Reset screen to on state
-        turnBatteryStatsAutoResetOn(); // Turn Battery Stats auto resetting back on
-        super.tearDown();
-    }
-
     public void testPartialWakelock() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
         resetBatteryStats();
-        unplugDevice();
-        flushBatteryStatsHandlers();
+        DeviceUtils.unplugDevice(getDevice());
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
         // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
         // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
-        String aodState = getAodState();
-        setAodState("0");
-        turnScreenOff();
+        String aodState = DeviceUtils.getAodState(getDevice());
+        DeviceUtils.setAodState(getDevice(), "0");
+        DeviceUtils.turnScreenOff(getDevice());
 
         final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
         Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
@@ -105,15 +131,17 @@
         // Add state sets to the list in order.
         List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
 
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, atomTag, true);  // True: uses attribution.
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testWakelockState");
 
         // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         //=================== verify that statsd is correct ===============//
         // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurred(stateSet, data,
                 atom -> atom.getWakelockStateChanged().getState().getNumber());
 
         for (EventMetricData event : data) {
@@ -126,28 +154,30 @@
 
     @RestrictedBuildTest
     public void testPartialWakelockDuration() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
 
         // getUid() needs shell command via ADB. turnScreenOff() sometimes let system go to suspend.
         // ADB disconnection causes failure of getUid(). Move up here before turnScreenOff().
-        final int EXPECTED_UID = getUid();
+        final int EXPECTED_UID = DeviceUtils.getAppUid(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
 
-        turnScreenOn(); // To ensure that the ScreenOff later gets logged.
+        DeviceUtils.turnScreenOn(getDevice()); // To ensure that the ScreenOff later gets logged.
         // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
         // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
-        String aodState = getAodState();
-        setAodState("0");
+        String aodState = DeviceUtils.getAodState(getDevice());
+        DeviceUtils.setAodState(getDevice(), "0");
         uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         resetBatteryStats();
-        unplugDevice();
-        turnScreenOff();
-        flushBatteryStatsHandlers();
+        DeviceUtils.unplugDevice(getDevice());
+        DeviceUtils.turnScreenOff(getDevice());
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
 
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
-        Thread.sleep(WAIT_TIME_LONG); // Make sure the one second bucket has ended.
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testWakelockState");
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); // Make sure the one second bucket has ended.
 
 
         final String EXPECTED_TAG = "StatsdPartialWakelock";
@@ -169,33 +199,35 @@
                 EXPECTED_UID, EXPECTED_TAG
         ).that(statsdDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
 
-        setAodState(aodState); // restores AOD to initial state.
+        DeviceUtils.setAodState(getDevice(), aodState); // restores AOD to initial state.
     }
 
     public void testPartialWakelockLoad() throws Exception {
         if (!ENABLE_LOAD_TEST) return;
-        turnScreenOn(); // To ensure that the ScreenOff later gets logged.
+        DeviceUtils.turnScreenOn(getDevice()); // To ensure that the ScreenOff later gets logged.
         uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         resetBatteryStats();
-        unplugDevice();
-        turnScreenOff();
+        DeviceUtils.unplugDevice(getDevice());
+        DeviceUtils.turnScreenOff(getDevice());
 
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockLoad");
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testWakelockLoad");
         // Give time for stuck wakelocks to increase duration.
-        Thread.sleep(10_000);
+        RunUtil.getDefault().sleep(10_000);
 
 
         final String EXPECTED_TAG = "StatsdPartialWakelock";
-        final int EXPECTED_UID = getUid();
+        final int EXPECTED_UID = DeviceUtils.getAppUid(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         final int NUM_THREADS = 16;
         final int NUM_COUNT_PER_THREAD = 1000;
         final int MAX_DURATION_MS = 15_000;
         final int MIN_DURATION_MS = 1_000;
 
 
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
+//        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
+//        HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
 
         // TODO: this fails because we only have the hashes of the wakelock tags in statsd.
         // If we want to run this test, we need to fix this.
@@ -256,7 +288,8 @@
     // TODO: Refactor these into some utils class.
 
     public HashMap<Integer, HashMap<Long, Long>> getStatsdWakelockData() throws Exception {
-        StatsLogReport report = getStatsLogReport();
+        StatsLogReport report = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         CLog.d("Received the following stats log report: \n" + report.toString());
 
         // Stores total duration of each wakelock across buckets.
@@ -607,13 +640,14 @@
                         .addPredicate(screenIsOffId)
                         .addPredicate(deviceIsUnpluggedId));
 
-        StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addDurationMetric(DurationMetric.newBuilder()
-                .setId(metricId)
-                .setWhat(partialWakelockIsOnId)
-                .setCondition(screenOffBatteryOnId)
-                .setDimensionsInWhat(dimensions)
-                .setBucket(bucketsize))
+                        .setId(metricId)
+                        .setWhat(partialWakelockIsOnId)
+                        .setCondition(screenOffBatteryOnId)
+                        .setDimensionsInWhat(dimensions)
+                        .setBucket(bucketsize))
                 .addAtomMatcher(wakelockAcquire)
                 .addAtomMatcher(wakelockChangeAcquire)
                 .addAtomMatcher(wakelockRelease)
@@ -639,6 +673,10 @@
                 .addPredicate(screenIsOff)
                 .addPredicate(screenOffBatteryOn);
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
+    }
+
+    private void resetBatteryStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys batterystats --reset");
     }
 }